You are on page 1of 240

1

Por ltimo, gracias a nuestras esposas, Miriam Tenenbaum, Vivienne Lang-


sam y Gail Augenstein por sus consejos y su impulso durante la larga Y ardua tarea
de producir este libro.
1
AARON TENENBAUM

YEDIDY AH LANGSAM

MOSHE AUGENSTEIN Introduccin


a la estructura de datos

Una computadora es una mquina que maneja informacin. El estudio de la ciencia


de la computacin incluye saber la forma en que se organiza la informacin en una
computadora, cmo puede manejarse y la manera en que puede utilizarse esa infor-
macin. Por tanto, es de suma importancia para un estudiante de la ciencia de la
computacin, entender los conceptos de organizacin y manejo de informacin para
continuar el estudio de la disciplina.

1.1 INFORMACION Y SIGNIFICADO

Si la ciencia de la computacin es fundamentalmente el estudio de la informacin, la


primera pregunta que surge es: qu es la informacin? Por desgracia, aunque ese
concepto es la piedra angular de la disciplina, la interrogante anterior no pu,de con-
testarse con precisin. En este sentido, el concepto de informacin en esta disciplina
es similar a los conceptos de punto, recty plano para la geometra: son un grupo de
trminos indefinid.os con los que se pueden hacer proposiciones, pero no pueden
definirse con conceptos ms elementales.
En la geometra es posible hablar de la lortgitud'de una recta, a pesar de que el
concepto de recta es en s mismo indefinido. La longitud de una recta es una medida
cuantitativa. De manera similar, en la ciencia del computacin podemos hablar de
cantidades de informacin. La unidad bsica de informacin es el bit, cuyo valor
confirma una de dos posibilidades excluyentes. Por ejemplo, un conmutador puede
estar en una de dos posiiones pero no en ambas al mismo tiempo, pero el hecho es
que el estar en la posicin de "encendido" o en la de "apagado" representa un bit

xiv Prefacio 1
de informacin. Si un dispositivo puede estar en ms de dos estados, al tomar un
estado particular se tiene ms de un bit de informacin. Por ejemplo si un disco
Conmutador l
selector tiene ocho posibles posiciones, al estar en la posicin 4 descarta las otras siete
posibilidades; de la misma forma, cuando un conmutador est en la posicin de 1 OFF 1
"encendido", descarta la nica posibilidad restante.
Otra manera de pensar en este fenmeno es la siguiente: supongamos que slo ON 1
tenemos conmutadores de dos posiciones, pero podemos utilizar tantos como necesi-
temos. Cuntos de dichos dispositivos sern necesarios para representar un disco (a) Un conmutador (dos posibilidades)
selector con ocho posiciones? Claramente, un conmutador nada m.s puede represen>
tardos posiciones(Fig. 1.1.a). Dos conmutadores bastaran para cuatro posiciones
diferentes (Fig. 1.1.b), y se requeriran tres de stos para ocho posiciones diferentes ConmutadO'r 1 Conmutador 2
(Fig. l. l.c). En general, n apagadores pueden representar 2" posiciones diferentes.
Los dgitos binarios O y 1 se usan para representar dos estados posibles de un OFF 9FF
bit particular (en realidad la palabra bit es una contraccin de las palabras "binary
digit"). Dado n bits, se utiliza una cadena formada con n dgitos 1 y O para represen- OFF ON
1
tar una configuracin. Por ejemplo, la cadena 101011 representa 6 conmutadores: el
primero est "encendido" (1), el segundo "apagado" (O), el tercero encendido, el ON OFF
1
cuarto apagado, y el quinto y el sexto encendidos.
Hemos visto que tres bits son suficientes para representar ocho posibilidades. ON ON
Las ocho configuraciones posibles de esos fres bits (000, 001, 010, 011, 100, 101, 110 1
y 111) pueden utilizarse para representar los enteros del O al 7. Sin embargo, no hay
(b)_Dos conmiadores (cuatrO posibilidades)
nada intrnseco en esas configuraciones que impliquen que cada una de ellas repre-
sente un entero en particular. Cualquier asignacin que se haga de valores. enteros;
para ciertas combinaciones de bits es vlida siempre y cuando dos valores .enteros no
. Conmutador 1 Conmutador 2 Conmutador 3
se asignen a una misma configuracin. Una vez que se ha determinado tal asigna-
cin, cualquier combinacin particular de bits puede interpretarse sin ambigedad Off OFF OFF
como un entero especfico. Examinemos algunos mtodos muy usados para la
interpretacin de combinaciones de bits como enteros. OFF OFF ON
Enteros binarios y decimales
OFF 1 ON . OFF
El mtodo utilizado con mayor frecuencia para interpretar combinaciones de
bits como enteros no negativos es el sistema de nmeros binario. En ese sistema, cae OFF ,ON ON
1
da posicin de bit representa una potencia de 2. La posicin de la extrema derecha
representa 2(que es igual a 1), la siguiente posicin a la izquierda es 2 1 (igual a 2), la 1
ON OFF OFF
1 1
siguiente significa 22 (que es cuatro), y as sucesivamente. Un entero se representa
como suma de potencias de 2. Una cadena compuesta nicamente de ceros represen- ON
ta al nmero O. Si aparece el dgito 1 en una posicin de un bit partjcular, la potencia
1 1 OFF 1 1 ON
de 2 que representa dicha posicin se incluye en la suma; pero si hay un O, no.se toma
en cuenta. Por ejemplo, el grupo de bits 00100110 tiene un 1 en las posiciones l., 2 y 5 1 ON
.1 ON
1 1 OFF
(si se cuenta de derecha a izquierda y si se considera la posicin de la extrema de-
recha como la posicin O). As, 00100110 representa al entero 2 1 + 22 + 25 = 2 + 4 ON ON ON
+ 32 = 38. Segn esta interpretacin, cualquier cadena de bits cuya longitud sean
representa un nmero entero nico no negativo entre O y 2n-_ 1; asimismo_, cualqyier (e) Tres conmu'tadof'es (ocho posibilidades)
entero no negativo entre O y 2n-l puede representarse por una sola cadena de bits de
longitud n. \ Figura 1.1.1

2 Estructuras de datos en e
Introduccin 'a lstructura de datos
3
Hay dos mtodos muy utilizados para representar nmeros binarios negativos. Nmeros reales
En el primero, llamado notacin de complemento a uno, un nmero negativo se
representa cambiando el valor absoluto de cada bit al valor opuesto. Por ejemplo, El. mtodo .~sual que emplean las computadoras para representar nmeros
como 00100! !O representa al 38, 110! 100! se emplea para representar al -38. Esto reales es la notacw~ de punto flotante. Hay muchas variaciones de esta notacin y
significa que el bit de la extrema izquierda ya no se utilizar para representar una po- cada una de ellas tiene caractersticas individuales. El concepto clave es que un n-
tencia de 2 sino que se reservar para el signo del nmero. Una cadena de bits que mero real se represema por un nmero, llamado mantisa, multiplicado por una base
comienza con O representa un nmero positivo, mientras que una que comienza con el~vada a una pNencia entera, llamada exponente. Por lo comn, para representar
I representa un nmero negativo. Dados n bits, la cantidad de nmeros que pueden n.umeros r~ales .diferentes la base se fija y la mantisa y el exponente varan. Por
representarse abarca desde -2tn-ll + 1 (un 1 seguido por n-1 ceros) hasta 2tn-ll eJemplo:
3 s1 se fiJa la base como 10' el nmero. 387 53 po d na
representarse como
- l (un O seguido por n-l unos). Obsrvese que segn esta convencin existen dos 3 753 x 10-2 (recuerde que 10-2 es .01). La mantisa es 38753 y elexpone~te -2
representaciones para el nmero O: un O "positivo" en el que todos los bits son O y Otras representaciones
t d d posibles son .38753 x JQ3 y 387 53 x JO. E scogemos . ,a
un O "negativo" en el que todos los bits son l. represen ac10n on e la mantisa es un entero sin ceros al final.
El segundo mtodo de representacin de nmeros binarios negativos se llama .. En la notacin de punto flotante que describimos (la cual no necesariamente
notacin de complemento 2. En sta se suma un l. a la representacin del comple- esta implantada tal y como est descrita en una mquina particular), un nmero Tea!
mento a 1 del nmero negativo. Por ejemplo: como 1.1011001 se usa para represen- se representa por una. cad,ena de 32 bits en la que la mantisa tiene 24 bits y le sigue
tar -38 en notacin de complemento a 1 11011010 se usa para representar -38 en un exponente con 8 bits. La base est fijada como 10. Ambos, la mantisa y el expo-
notacin de complemento a 2.. Dados n bits la cantidad de nmeros que puedan nente son binarios en notacin de complemento a 2 -Por_ eiempl o, aTepre-
enteros
b''
representarse desde -2tn-ll (un l. seguido por n~ 1 ceros) hasta 2<n-l) -1 (un cero sentacion
b mana .con 24 bits de 38753 es 000000001001011101 !0000! Y a represen-
seguido por n-1 l). Ntese que -2<n-i\ puede representarse .en notacin de t ac10n mana de -2 en complemento a 2 con 8 bits es: 111111 !O; la de 387 53 es
complemento a 2 pero no en notacin de complemento a 1. Sin embargo su valor ab- 00000000100!01110110000111111 l 10. Otros nmeros reales y sus representa~iones
soluto 2tn-1> no puede representarse en ninguna de ambas usando n bits. Observe de punto flotante son:
tambin que existe una sola representacin del O usando n bits en notacin de
complemento a 2. Para ver esto, considere que para el O se usan ocho bits 00000000.
El complemento a 1 es 111111 ! 1, que es el O negativo en esta notacin. Al sumar 1 o
para obtener la forma de complemento a 2 se obtiene 100000000, que tiene nueve

100 00000000000000000000000100000010
bits de largo. Dado que slo se permiten 8 bits, el de la extrema izquierda (o desbor-
.5
damiento) se descarta, por lo cual 00000000 como menos O. 101 U 111111
De ninguna manera el sistema de nmeros binario es el nico mtodo mediante .000005 00000000000000000000010111111010
el cual pueden representarse nmeros enteros usand,o bits. Por ejemplo: una cadena
de bits puede usarse para representar enteros en el sistema de nmeros decimal de la 12000 0000000000000000000011000000001!
siguiente manera. Pueden emplearse cuatro bits para representar un dgito decimal -387.53 llllllll01101000100llllllllllll0
entre O y 9 en la notacin binaria descrita previamente. Una cadena de bits de longi-
tud arbitraria se puede dividir en conjuntos consecutivos .de cuatro bits, donde cada -12000 l l l l l l l l l l l l l l l l l l l lOlOOOOOOOOI.l
conjunto representa un dgito decimal. De esta forma, la cadena representar el n-
mero formado por esos dgitos decimales en la notacn dcima! convencional. Por .La ventaja de la notacin de punto flotante es que puede usarse para represen-
ejemplo, en este sistema la cadena de bits 00100110 se separa en dos cadenas de t~r numeres co~ valor absoluto muy grande o pequeo. Por ejemplo, en la'notacin
cuatro bits cada una: 0010 y O! !O. La primera de ellas representa al dgito decimal 2 vista antes,,. el numero
y la segunda al 6, por lo que la cadena entera representa al entero 26. Esta represen- d~ . mayor que puede
. representarse
. es (221-1) x1012, ,~ea~
.
eramente un numero muy grande. El menor nmero positivo que puede repre-
tacin se llama decimal codificado en binario. sentarse es 10- 123, el cual es ciertamente pequeo. El factor que limita la precisin
Una caracterstica importante de la representacin de enteros no negativos en con la.que pued~n ":Pr'.:s~ntarse nmeros en una mquina en particular es el nmero
el sistema decimal codificado en binario es que no todas las cadenas de bits son de dig1tos bmanos s1grnf1cat1vos en la mantisa. No todos los nmeros entre el mayor
representaciones vlidas de un entero decimal. Cuatro bits pueden representar una Y el .n:ien?r pueden representarse. Nustra representacin permite solamente 23 bits
de 16 posibilidades diferentes, dado que hay 16 estados posibles para un conjunto de 51.grnficativos: .Por. tanto, un nmero como 10 millones 1 ~que requiere 24 dgitos
cuatro bits. Sin embargo, en la representacin de enteros en decimal codificado en bmanos s1.gn,ficaUvos en la mantisa- debera aproximarse por 10 millones (1 X
binario se utilizan solamente 10 de estas posibilidades. Esto es, cdigos como 1010 y 10 7) que solo requiere un dgito significativo. '
1!00, cuyos valores binarios son mayores o iguales que 10, no son vlidos en un
nmero decimal codificado en binario.
Estructuras de datos en C Introduccin a la estructura de datos 5
4
Cadenas de caracteres c?noce como el tipo de datos. Hemos presentado varios tipos de datos: enteros bina-
rios, enteros decimales no negativos codificados en binario, nmros reales y cade-
Como todos sabemos, la informacin no se interpreta siempre numricamente. nas de caracteres. Las pr<:guntas clave son cmo determinar los tipos de datos que se
Los nombres, ttulos de trabajo y direcciones tambin tienen que representarse de pued~~ aprovechar P.ara interpretar patrones de bits y los tipos de datos que habrn
alguna forma en una computadora. Para permitir la representacin de tales objetos no de uhhzarse para la interpretacin de un patrn de bits particular.
numricos, es necesario otro mtodo adicional de interpretacin de cadenas de bits.
Tal informacin se representa por lo general en forma de cadenas de caracteres. Por Hardware y software
ejemplo, en algunas computadoras los ocho bits 00100110 representan el carcter
'&'. Una pauta diferente de ocho bits representa el carcter 'A', se'sa otro para 'B', La memoria. (tambin llamada almacenamiento o memoria principal) de una
otro para 'C' e incluso otros ms para cada carcter que tenga una representacin en computador~ es simplemente un grupo de bits (conmutadores). En todo momento
una mquina en particular. Una mquina rusa usa pautas de bits para representar de la op:rac1n de una computadora cualquier bit particular en la memoria es o O 1
caracteres rusos, mientras que una israeli lo hace para representar caracteres' hebreos. (encendido.o apa.gado). El 1 o O que contiene un bit se llama valor O contenido.
(En realidad los caracteres empleados resultan transparentes para la mquina; el Los bits estan agrupados en unidades ms grandes (como bytes) en la memoria
conjunto de caracteres puede cambiarse usando una cadena de impresin diferente de la computadora. En algunas computadoras se agrupan varios bytes en unidades
en la impresora.) lla~adas pala~ras. A cada una de estas unidades (byte o palabra, segn sea la m-
Si 8 bits representan un carcter, entonces se pueden representar hasta 256 ca- quina) .se le as1g~a una direccin; es decir, un nombre que identifica en la memoria a
racteres diferentes, dado que hay 256 patrones de ocho bits diferentes. Si la cadena una unidad particular del resto. Esta direccin suele ser numrica, de tal manera que
11000000 se utiliza pa.ra simbolizare! .carcter 'A' y. 11000001 para el 'B', l cadena P?de~?s hablar del byte 746 o de la palabrn 937. Es frecuente llamar localidad a una
de bits l 100000011000001 podra representar la cadena de caracteres' AB!. En gene- d1rec~1on; adems, .los contenidos de una localidad son los valores de los bits que
ral, una cadena de caracteres (STR) se representa mediante la concatenaciride las constituyen una unidad en esa localidad.
cadenas de bits que representan los caracteres individuales. . . Toda computad~ra tiene un conjunto de tipos de datos "nativos", lo cual sig-
Como en el caso de los enteros, no hay nada en una cadena de bits particular mf1ca que .se constr~yo con un mecamsmo para manejar pautas de bits, compatible
que la haga intrnsecamente apropia~a para representar un carcter especfico. La conios obJet~s que e?tos representan. Por ejemplo, suponga que una computadora
asignacin de cadenas de bits a caracteres es por completo arbitraria, pero debe asig- co~t1ene una mstr~cc1n para sumar dos enteros binarios y poner su suma en una 0
narse de manera consistente. Es conveniente apegarse a una regla para la asignacin cahdad de memona para su uso posterior. Entonces tiene que existir un mecanismo
de cadenas de bits a caracteres. Por ejemplo, dos cadenas de bits pueden asignarse a dentro de la computadora para
dos letras, de manera que aquella con menor valor binario sea asignada a la letra que
primero aparece en el alfabeto. Sin embargo, tal regla no ser ms que una conve- l. Extraer. de dos localidades dadas pautas de bits de los operandos. .
niencia; no est regida por una relacin intrnseca entre cadenas de bits y caracteres. En 2. Producir una tercera pauta de bits que represente al entero binario, el cual es
realidad las computadoras tambin difieren en cuanto al nmero de bits empleados la suma de los dos enteros binarios representados por los do.s operandos.
para representar caracteres. Algunas computadoras u_san 7 bits (y por consiguiente 3. Almacenar la pauta de bits resultante en una localidad dada.
permiten slo hasta 12.8 caracteres), otras usan 8 (hasta 256 can1cteres) y algunas ms
usan 10 (hasta 1024 caracteres posibles). El nmero de bits necesario para represen- La computadora "sabe" interpretar la pauta de bits de las localidades de memoria
tar un carcter en una computadora se llama tamao de byte y un grupo con ese dadas ~1:' enteros binarios porque el hardware que ejecuta esa instruccin particu-
nmero de bits se llama byte. lar esta diseado para hacerlo. Esto es similar a una luz que "sabe" estar encendida
Note que cuando se usa ocho bits para representar un carcter significa que cuando el conmutador est en una posicin particular. . .
pueden representarse 256 caracteres posibles. No es muy frecuente encontrar una Si la misma mquina tiene tambin una instruccin para sumar dos nmeros
computadora que emplea tantos caracteres distintos (aunque es concebible que t1na reales, ~ebe existir un mecani?mo interconstruido para interpretar los operandos
computadora que emplee tantos caracteres distintos (aunque.es concebible que una c?mo numeros. reales .. ~e reqmeren dos instrucciones distintas para las dos opera-
vas, negritas y otros caracteres) por lo que muchas de las combinaciones de ocho bits c10nes, Y cada ms.trucc1on porta consigo una identificacin implcita de los tipos de
no se usan para representar caracteres. s.us operandos, as1 como sus localidades explcitas.,Es, por consiguiente, responsabi-
As, vemos que la informacin por s misma no tiene significado., Puede asig- hdad del progr~i:nador con?.cer los tipos de datos que estn contenidos en cada loca-
narse cualquier significado a una pauta particular de bits, siempre y cuando esto se lidad que se ut1hza. Tamb1en incumbe al programador elegir el uso de adicin de
haga de manera consistente. Es la interpretacin de una pauta de bits la que 1~ con- reales o de enteros para obtener la suma de dos nmeros. ,
fiere significado. Por ejemplo, la cadena de bits OOIOOIIOpuede interpretarse como . Un l~nguaje de programacin de alto nivel ayuda mucho en esta tarea. Por
el nmero 38 (binario), como el nmero 26 (decimal codificado binario), o como el eJemplo, s1 un programador en C declara
carcter '&'. Un mtodo de interpretacin de una pauta de bits con frecuencia se

6 Estructuras de datos en C Introduccin a la estructura de datos


7
---------------------
int X, y; lgicas. Una vez que se define el tipo de datos abstracto y se especifican las opera-
float a, b; ciones permitidas que involucran a este tipo (o uno muy parecido), puede implantar-
se. Una implantacin puede ser una implantacin de hardware en la cual se disea y
se reserva espacio en cuatro localidades para cuatro nmeros diferentes. Los identi- construye como parte de una computadora el circuito necesario para ejecutar la
jicadores x, y, a, y b, pueden hacer referencia a esas 4 localidades. Se usa un identifi- operacin requerida, o una implantacin de software, en la cual se escribe un
cador en lugar de una direccin numrica para referirse a una localidad particular de programa que consta de instrucciones existentes en el hardware para interpretar en
memoria porque as conviene al programador. Los contenidos de las localidades re- la forma deseada las cadenas de caracteres y ejecutar las operaciones requeridas. As
servadas para x y y, sern interpretados como enteros mientras que los de a y b sern que una implantacin de software incluye una especificacin de cmo se representa
interpretados como nmeros de punto flotante. El compilador, que es responsable un objeto del tipo de datos nuevo por medio de objetos de tipos de datos ya existen-
de traducir los programas en Cal lenguaje de la mquina traducir el"+'' en la ins- tes, de acuerdo tambin a una especificacin de cmo se maneja un objeto.
truccin , ,
En lo que resta del libro, usaremos el trmino "implantacin" en el sentido de
implantacin de software.
X= X+ y;
Un ejemplo
como adicin de enteros, y el "+" en la instruccin
Ilustremos los conceptos con un ejemplo. Supongamos que el hardware de una
a= a+ b; computadora contiene una instruccin

como suma de puntos flotantes. Un.opetador como"+" en realidad es un operador MOVE (source, dest, length)
genrico pues tiene varios significados diferentes dependiendo del contexto. El ~om-
pilador libera al programador de la especificacin del tipo de suma que debe e1ecu- que copia una cadena de caracteres de una longitud de bytes de una direccin especi-
tarse al examinar el contexto y usar ia versin adecuada. ficada por origen a una direccin especificada por destino. (Escribimos las instruc-
Es importante reconocer el papel clave que desempean las declaraciones e? _un ciones de hardware con maysculas. La longitud debe especificarse mediante un
lenguaje de alto nivel, pues por medio de declaraciones el programador espec1f1ca entero, por lo cual la escribimos con minsculas. Orden y destino pueden especificarse
cmo el programa debe interpretar los contenidos de la memoria. Al hacerlo, una por medio de identificador.es que representen localidades de memoria.) Un ejemplo de
declaracin especifica cunta memoria se necesita para una entidad particular, cmo esta instruccin es MOVE (a, b, 3), que copia los tres bytes que inician en la locali-
deben interpretarse los contenidos de dicha memoria y otros detalles vitales. Las dad a, en los primeros tres bytes que inician en la localidad b.
declaraciones especifican tambin con exactitud al compilador el significado de los Observe los diferentes papeles que desempean los identificadores a y ben esta
smbolos de operacin que se usan subsecuentemente. operacin. El primer operando de la instruccin MOVE consiste en los contenidos
de la localidad especificada por el identificador a. El segundo operando, sin embar-
El concepto de implantacin go, no consta de los contenidos de la localidad b ya que son irrelevantes para la eje-
cucin de la instruccin. Ms bien, la localidad misma es el operando pues especifica
Hasta ahora hemos visto los tipos de datos como un mtodo para interpretar el destino de la cadena de caracteres. Aunque un identificador se usa siempre para
los contenidos de la memoria de una computadora. El ci;mjuntode tipo de.datos na- denotar una localidad, es comn que se use para referir los contenidos de esa locali-
tivos que puede tener una computadora est determinado por las funciones que han dad. Siempre es evidente por el contexto si se usa un identificador para referirse a
sido alambradas dentro de su hardware. Sin embargo, podemos ver el concepto una localidad o a sus contenidos. El identificador que aparece como el primer ope-
de tipo de datos desde una perspectiva muy diferente; es decir, no verlo en trminos de rando de la instruccin MOVE se refiere a los contenidos de memoria, mientras que
lo que puede hacer una computadora sino en trminos de lo que quiere el usuario ha- el segundo se refiere a la localidad.
cer. Por ejemplo, si deseamos obtener la suma de dos enteros, no necesitamos preo- Suponemos tambin que el hardware de la computadora contiene las instruc-
cuparnos por el mecanismo detallado mediante el cual se obtendr la suma. Nos ciones aritmticas y de saltos normales que indicamos al usar una notacin similar a
interesa la manipulacin del concepto matemtico de suma y no la manipulacin de la de C. Por ejemplo, la instruccin
los bits de hardware. Puede usarse el hardware de la computadora para representar
un entero y es til slo en cuanto sea exitosa la representacin. Z = X + y;
Cuando separamos el concepto de "tipo de datos" de las capacidades del hard,
ware de una computadora, podemos considerar un sinnmero de tipos de datos. Un interpreta los contenidos de los bytes en las localidades x y y como enteros binarios,
tipo de datos es un concepto abstracto definido por un conjunto de propiedades los suma e inserta la representacin binaria de su suma en el byte de la localidad z.

8 Estructuras de datos en C Introduccin a !a estructura de datos 9


1
(Nosotros no operamos con enteros de longitud mayor que un byte e ignoramos la
posibilidad de desbordamiento.) De nuevo, aqu x y y se usan para referirse a los
contenidos de memoria, mientras que z se refiere a la localidad de memoria, pero la
interpretacin adecuada es clara por el contexto.
ta)
En ocasiones es deseable sumar una cantidad a una direccin para obtener
otra. Por ejemplo, si a es una localidad en la memoria, podramos querer referir-
nos a la localidad cuatro bytes ms all de a. No podemos hacerlo por medio de
a + 4, pues esta notacin est reservada para los contenidos enteros de la localidad
a + 4. En consecuencia introducimos la notacin a [4] para referirnos a dicha locali-
dad; tambin introducimos la notacin a [x] para referirnos a la direccin obtenida
al sumar a la direccin a, los contenidos binarios enteros del byte en x. (b)

La instruccin MOYE exige especificar al programador la longitud de la cade-


na por copiar. As, su operando es una cadena de.caracteres de longitud fija (esto es,
debe conocerse la longitud de la cadena). Una cadena de caracteres de longitud fija y
un entero binario del tamao de un byte pueden considerarse tipos de datos nativos
( l I I 1 I I I I I I I I I I I I
14 H E L L O E V E R Y B O D Y J
de esta mquina en particular.
Suponga que deseamos implantar en esta mquina cadenas de caracteres .de (e)
longitud variable. Es decir, queremos que los programadores puedan usar la instruc-
cin Figura 1.1.2 G?dena de caracteres" de longitud variable.

MOVEVAR (source, dest) De igual manera podemos implantar una operacin CONCATVAR (el, e2, e3) para
concatenar dos cadenas de caracteres de longitud variable el y e2, y colocar el resul-
para mover una cadena de caracteres de la localizacin origen a la localizacin desti- tado en e3. La figura l. l.2c muestra la concatenacin de las dos cadenas de caracte-
no sin estar obligados a especificar ninguna longitud. res de las figuras l. l.2a y 1. l.2b:
Para implantar este nuevo tipo de datos, tenemos que decidir. primero cmo
debe representarse en la memoria de la mquina e indicar despus cmo debe mane-
jarse esta representacin. Claramente, es necesario conocer cuntos bytes deben mo, I* se meve la longitud. */
z = c:L + c2;
verse para ejecutar esta instruccin. Como la operacin MOVEV AR no especifica el
MOVE(z, c3, 1);
nmero, debe incluirse en la representacin misma de la cad.ena de caracteres. Una
cadena de caracteres de longitud variable. de longitud/, puede representarse por me- \ I *' se mueve la primera cadena */ -
for (i = 1; i <= el; MOVE(cl[il, c3[il, 1);
dio de un conjunto contiguo de/+ 1 bytes (/ < 256), El primer byte contiene la I * se mueve la segunda cadena */
representacin binaria de la longitud I y los bytes restantes contienen la representa- for ( i = 1; i <= c2) {
cin de los caracteres en la cadena. Las representaciones de estas tres cadenas se X = c1 + i;
muestran en la figura l. l .2. [Observe que los dgitos 5 y 9 en esas figuras no estn CO' MOVE(c2[il, c3[xl, 1);
locadas para representar las pautas de bits de los caracteres '5' y '9' sino las pautas !* fin defor *!
00000101 y 00001001 (si suponemos 8 bits en un byte), que representan a los enteros
5 y 9. De manera similar, en la figura l. l.2c el 14 est representado por la pauta de Sin embargo una vez que la operacin MOYEV AR ha sido definida, CONCATV AR
bits 00001110. Observe tambin que esta representacin es muy diferente de lama- puede implantarse al usar MOVEV AR como sigue:
nera en que son implantadas realmente las cadenas de caracteres en el lenguaje C.]
El programa para implantar la operacin MOYEV AR puede escribirse como
sigue (i es una localidad de memoria auxiliar):
MOVEVAR(c2, c3[c1]); I* se mueve la segunda cadena *
MOVEVAR(c1, c3); I* se mizeve la Piinera adena */
Z=c1+c2; !* se calcula la longitud de la Cadena. resulante *
MOVE(source, dest, 1); MOVE(z, c3, 1);
fer (i=1; i < dest; i++)
MOVE(source[iJ, dest(iJ, 1);
.1 La figura l.1.3 muestra las fases de esta operacin con las cadenas de la figura l.1.2 .
Aunque la ltima versin es ms corta, no es necesariamente ms eficiente, pues se

ij. . 1O Estructuras de datos en C Introduccin a la estructura de datos 11


.

.
ejecutan todas las instrucciones usadas al implantar MOVEV AR cada vez que se usa
Cl
esta operacin.
La instruccin z = el + c2 en los dos algoritmos anteriores tiene particular in-
ters. La instruccin de suma opera en forma independiente del uso que tengan sus
operandos (en este caso, partes de cadenas de longitud variable). La instruccin est
diseada para tratar a sus operandos como enteros de un solo byte, sin tomar en
cuenta ningn uso que pueda darles el programador. De manera similar, al c3[cl]
C2 refiere a la localidad cuya direccin est dada al sumar los contenidos del byte en la
localidad el en la direccin c3. As, se considera que el byte en el contiene un entero
i binario, aunque tambin es el principio de una cadena de caracteres de longitud va-
riable, lo cual ilustra el hecho de que los tipos de datos son un mtodo para tratar los
contenidos de la memoria, los cuales no tienen un significado propio.
Observe que esta representacin de cadenas de caracteres de longitud variable
solamente permite cadenas cuya longitud es menor o igual al mayor entero binario
que corresponde a un byte. Si un byte tiene och.o bits, esto significa que la cadena
ms grande posible es de 255 (2 8- 1) caracteres. Para permitir cadenas ms grandes
debemos escoger otra representacin y escribir otro conjunto de programas. Si usa-
mos esta representacin de ca,denas de caracteres de longitud variable, la operacin
de .concatenacin quedar invalidada en caso de que la cadena resultante sea de una
longitud mayor a los 255 caracteres. Como el resultado de esta operacin no est de-
(a) MOVEVAR (C2, C3[Cll);
finido, pueden implantarse diversas acciones si intentamos realizar la operacin.
Una posibilidad es usar solamente lo.s primeros 255 caracteres del resultado: Otra, es
ignorar por completo la operacin y no mover nada.al campo de resultado, Tambin
C3 existe la posibilidad de imprimir un mensaje de advertencia o de suponer que el

i usuario quiere lograr cualquier resultado que decida.


En realidad, el lenguaje C utiliza una implantacin de. cadenas de caracteres
por completo diferente que evita esta limitacin en la longitud de las cadenas. En el
lenguaje C, todas las cadenas terminan con el carcter especial ' \ O' el compilador lo
(b) MOVEVAR (Cl, C3); coloca de manera automtica al final de cada cadena, y dicho carcter nunca apare-
ce cuando se escribe la cadena. Como no sabemos con antelacin la longitud,
todas las operaciones con cadenas deben realizarse en un carcter a la vez hasta
encontrar ' \O'.
El programa para implantar la operacin MOVERV AR bajo esta implanta-
cin, puede escribirse como sigue:

i = D;
while (source[iJ != '\0 1 )
MOVE(source[il, dest[il, 1);
C3
i++;

i dest[iJ = 1 \0 1 ;
I* se indica terminacin de cadena de destino con ' \O' */
(e) Z = Cl + C2; MOVE (Z, C3, 1); Para implantar la operacin de concatenacin CONCATVAR (el, c2, c3)
podemos .escribir:
Figura 1.1.3 Las operaciones CONCATVAR.

12 Estructuras de datos en C Introduccin a la estructura de datos 13


i = O propiedades lgicas y matemticas de un tipo de datos o estructura, el ADT es un
I * se mueve la primera cadena *I principio muy til para quienes realizan las implantaciones y una herramienta til
while (c1[il != '\0') ( para los programadores que desean usar correctamente los tipos de datos.
MOVE(c1[il, c3[il, 1); . Hay muchos mtodos para especificar un ADT. El mtodo que usamos es se-
i++: miformal Yse parece mucho a la notacin del lenguaje C aunque la ampla cuando es
necesario. Para ilustrar tanto el concepto de ADT como nuestro mtodo de especifi-
/* se mueve la segunda cadena *I cacin, considrese el ADT RA TIONAL, que corresponde al concepto matemtico
j = o;
while (c2[jl != '\0') de n~ero racional. Un nmero racional es un nmero que puede expresarse como
MOVE(c2[j++J, c3[i++l, 1); el cociente de dos enteros. Las operaciones que definimos con nmeros racionales
I * se indica terminacin de cadena de destino con un a \ O * / s?,n la creacin de u~ nmero racional a partir de dos enteros, la suma, la multiplica-
c3[iJ = '\0 1; c10n Y la prueba de igualdad. La especificacin inicial de este ADT es la siguiente:

Una desventaja de la implantacin en el lenguaje C de cadenas de caracteres es que


slo se sabr la longitud de la cadena, si se cuenta carcter por carcter y se en- I * definicin de valores * /
abstract typedef <integer, integer> RATIONAL;
cuentra el smbolo'\ O'. Esto est ms que compensado por la ventaja de no tener un
condition RATIONAL[1] <> O;
lmite arbitrario sobre la longitud de la cadena.
Una vez que se ha elegido una representaciri para objetos de un tipo de datos I * definicil)., de operadores * /
determinado y que se han escrito las rutinas para operar con esas representaciones, abstract RATIONAL makerational(a,b)
el programador tiene la posibilidad de usar dichos tipos de datos para resolver int a,b;
problemas. El hardware original de la mquina msclos programas para implantar precondition b <> o;
tipos de datos ms complejos que los que proporciona el hardware, pueden pensarse postcondition makerational[O] == a;
como una mquina "mejor" que la integrada solamente por el hardware. El progra- makerationa1[1] b;
mador de la mquina origina no tiene que preocuparse por la manera en cue est
diseada la computadora ni por cules circuitos se usan para ejecutar cada instruc- abstract RATIONAL add(a,b) I * escrito como a -f b *!
cin. El programador slo necesita saber cules instrucciones estn disponibles y RATIONAL a,b;
postcondition add[11 a[1l * b[1l;
cmo pueden usarse. De manera similar, al programador que utiliza la mquina add[OJ a[Ol * b[1J + b[Dl a[11;
"extendida" (sta consiste en hardware y software), o "computadora virtual'', co-
mo suele conocerse, no, necesita ocuparse de los detalles de cmo fueron implanta- abstract RATIONAL mult(a,b) I * escrito como a *b */
dos varios tipos de datos. Todo lo que tiene que conocer es cmo puede manipularse EATIONAL a,b;
el programador. postcondition mult[Dl == a[Ol b[Dl;
mult[1J a[1l * b[1l;
Tipos de datos abstractos
abstract equal(a,b) /*escrito como a= =b*!
Una herramienta til para especificar las propiedacjes lgicas de los tipos de RATIONAL a,b;
datos es el tipo de datos abstracto o ADT (por sus siglas en ingls), el cual es, funda- postcondition eqdal (a[Ol*b[1) == blOJa[1]);
mentalmente, una coleccin de valores y un conjunto de operaciones con esos valo-
res. La coleccin y las operaciones forman una construccin matemtica que puede Un ADT consiste en dos partes: una definicin de valor y una de operador. La
implantarse utilizando una estructura de datos particular, ya sea de hardware o de de valor define la coleccin de valores para el ADT y consta de dos partes: una
software. El trmino "tipo de datos abstracto" se refiere al concepto matemtico clusula de definicin y una de condicin. Por ejemplo, la definicin de valor para el
bsico que define el tipo de datos. ADT RA TIONAL postula que un valor RATIONAL consiste en dos enteros, el s-
Al definir como concepto matemtico un tipo de datos abstracto, no nos im- . gundo de los cuales es,distinto de O. Por supuesto, los dos enteros que forman un n-
porta la eficiencia de tiempo o espacio. Estos son. aspectos de la implantacin. En mero racional son el numerador y el denominador. Usamos notacin de arreglos
realidad, la definicin de un ADT no tiene nada que ver con las caracteristicas de su (corchetes) para indicar las partes de un tipo abstracto. . .
implantacin. Ni siquiera puede ser posible implantar un ADT en especfico al usar Las palabras reservadas abstract typedef introducen una definicin de valor y la
una pieza de hardware o un sistema de software particular. Por ejemplo, ya vimos pal~b_ra reservada condition se usa para especificar cualquier condicin sobre el tipo
que el ADT integer no es universalmente implantable. Sin embargo, al especificar las definido, En esta definicin, la condicin especifica que el denominador no puede

r
I 14
Estructuras de datos en C Introduccin a la estructura de datos 15
.

'
r ser O. Se requiere la clusula de definicin aunque no es necesaria la de condicin Sin embargo, para algunos tipos de datos, podemos considerar iguales a dos
valores con componentes desiguales. En efecto, esto sucede con los nmeros ra-
para todo ADT.
Despus de la definicin de valor sigue en forma inmediata la de operador. cionales; por ejemplo, los nmeros racionales l/2, 2/4, 3/6 y 18/36 son iguales sin
Cada operador se define como una funcin abstracta de tres partes: un encabezado, importar la desigualdad de sus componentes. Se consideran iguales dos nmeros ra-
las precondiciones opcionales y las postcondiciones. Por ejemplo, la definicin de cionales cuando lo son sus componentes en la forma reducida. (Esto es; cuando el
operador del ADT RA T/ONAL incluye las operaciones de creacin (makerationa{), numerador y el denominador han sido ambos divididos por su mximocOmn divi-
suma (add), y multiplicacin (mu!t) as como una prueba de igualdad (equa{). Consi- sor). Una manera de verificar la igualdad entre racionales consiste en expresarlos
deremos primero la especificacin para la multiplicacin pues esta es la ms simple; como fracciones reducidas y luego verificar la igualdad entre denominadores y nu-
contiene un encabezado y postcondiciones pero no precondiciones: meradores. Otra manera es la de comprobar si los productos cruzados (es decir, la
multiplicacin del denominador de uno por el numen;dor del otro) son iguales. Este
es. el mtodo que hemos usado para especificar la operacin abstracta equal.
abstract RATIONAL mult(a,b) /* eSdrito cOmo a *b *I La especificacin abstracta ilustra el papel de un ADT en cuanto definicin pu-
RATIONAL a,b; ramente lgica de un nuevo tipo de dat.os. Dos pares ordenados son desiguales si lo
postcondition mult[Ol a[Ol*b[Ol; son sus componentes considerados como coleccin de dos enteros, aunque pueden
mult[1l a[1l*b[1J;
ser iguales si se consideran como nmeros racionales. Es improbable que alguna
implantacin de nmeros racionales pruebe la igualdad a!formar, realmente;. los
El encabezado de esta definicin consta de las primeras dos lneas, que son iguales al productos cruzados, pues pueden ser demasiado grandes :,ara representarlos como
encabezado de una funcin en lenguaje C. La palabra reserva.da abstrae/ indica que enteros en la mquina. Es ms probable que una implantacin reduzca primero las
no se trata de una funcin en lenguaje <::: sino de la definicin de operador de un entradas a fracciones reducidas para verificar luego la igualdad de los componentes.
ADT. El comentario que inicia con la palabra reservada nueva written indica otra En efecto una implantacin razonable insistira en que makerational, suma y
forma de escribir la funcin. mu// slo produce nmeros racionales en trminos menores, Sin embargo, las defini-
La postcondicin especifica qu hace la operacin. En una postcondicin, se ciones matemticas tales como la de tipos de datos abstractos no estn relacionadas
usa el nombre de la funcin (en este caso mult) para denotar el resultado de la opera- con los detalles de la implantacin.
cin .. As, mult[O] representa el numerador y mu/t[l] el denominador del resultado, En realidad, el hecho de que puedan ser iguales dos racionales aun cuando sus
respectivamente. Esto es, especifica qu condiciones son verdaderas despus de componentes no lo son nos obliga a escribir de nuevo las postcondiciones para ma-
realizada la operacin. En este ejemplo, la postcondicin especifica que el numera' keralional, add y mu//. Es decir, si
dar del resultado de una multiplicacin racional es igual al producto entero de los
numeradores de las dos entradas y el denominador es igual al producto entero de los mO aO, bO
)~-

dos denominadores. mi al bl
La especificacin para la suma (add) es sencilla y tan slo afirma que:
no es necesario que 111.0 sea igual a aO*bO y que mi sea igual a al*bl, slo que
aO * b ! + a 1 * bO
+
al
bO
bl al * bl
mO*al *b l sea igual a m l *aO*bO. Una especificacin de un ADT ms precisa para un
RATlONAL es la siguiente:
:
La operacin de creacin (makerationa{) crea un nmero racional a partir
de dos enteros y contiene el primer ejemplo de una precondicin. E11 general las pre- I * definicin de valores */
condiciones especifican cualquier restriccin que deba satisfacerse antes de aplicar la abstract typedef<inti int> RATIONAL
condition RATIONAL[1] <> O;
operacin. En este ejemplo, la precondicin plantea que makerational no puede
aplicarse si el denominador es cero. I * definicin de Operadores */
La especificacin para igualdad (equal) es ms significativa y compleja en con- abstract equal(a,b) I * escr~to como a= =b * /
cepto. En general, dos valores cualesquiera en un ADT soniguales" si y slo si lo, RATIONAL a,b;
son los valores de sus componentes. Ciertamente, por lo comn suponemos que existe, postconaition ec/ual == (a[Dl*b[1J == b[Dl*a[1l);
una operacin de igualdad (y desigualdad) definida del modo anterior, por lo que no
necesitamos definir en forma explcita un operador de igualdad: La operacin de, abstract RATIONAL makerational(a,b) I * escrito como [a,bJ * /
asignacin (asignar el valor de un objeto al de otro) es otro ejemplo de una opera- int a,b;
cin asumida que se da por supuesta la mayora de las veces para un ADT y no se precondition b <> o;
especifica de manera explcita. postcondition makerational[O)*b ~*mak~r~tiorial['LJ

16 Estructuras de datos en C
Introduccin.a la estructura de datos 17
abstract RATIONAL add(a,b) /* escrito como a + b *I De otra manera, podramos definir un ADT stp2, cuyos valores fueran secuen-
RATIONAL a,b; cias de longitud fija de elementos de tipos especficos. En tal caso la
postcondition add [a[Dl b(1J + b(OJ a(1l, a(1l*bl1JJ especificaramos. '

abstract RATIONAL mult(a,b} !* escrito comoa*b *!


abstract typedef <tpO, tp1, tp2, ... , tpn> stp2;
RATIONAL a,b;
postcondition mult (a(DJ b(OJ, a(1J b[1J Claro que podramos querer especificar una secuencia de longitud fija, con to-
dos sus elementos de un mismo tipo. En ese caso escribiramos:
Aqu se define primero el operador equal, y se extiende el operador = = para
abstract typedef <<tp,n>> stp3;
la igualdad de racionales usando la clusula escrlbe. Este operador se usa despus
para especificar los resultados de las operaciones racionales subsecuentes (add y En este caso stp3 representa una secuencia de longitud n, donde todos sus elementos
mult). son del mismo tipo, tp.
El resultado de la operacin makerational en los enteros a y b produce un ra- Por ejemplo, usando la notacin anterior podemos definir los siguientes tipos:
cional que es igual a a!b, pero la definicin no especifica los valores reales del nume-
,rador y denominador resultantes. La especificacin para makerational introduce abstract typedet <<int>> intseq;
tambin la notacin [a, b] para el racional formado por a yb, la cual se usa despus !* secuencia de enteros de */
para la definicin'de add y mu/t; f* cualquier longitud *I
Las definiciones de add y mu// especifican' que sus resultados son iguales a los abstract typedef <integer, char, float> seq3;
resultados no reducidos de la operacin correspondiente, pero los componentes indi- I* secuencia de longitud 3 *!
!* consistente de un entero *I
viduales no son necesariamente iguales.
I* un carcter y un *I
Ntese otra vez, que al definir esos operdores no especificamos cmo deben
f* punto flotante *I
calcularse, sino slo cules deben set sus resultados. Cmo se calculan 1, est deter-
minado por su implantacin y no por su especificacin. abstract typedef <<int,10>> intseq;
I* secuencia de 10 enteros *I
Secuencias como definiciones de valor
abstract typedef <<,2>> pair;
Al desarrollar las especificaciones para varios tipos de datos, usamos a menudo I * secuencia arbitraria de *I
notacin terica de conjuntos para especificar los valores de un ADT. En particu-
I * longitud 2 O/

lar, es de gran ayuda usar la notacin de secuencias matemticas que introducimos


ahora. Dos secuencias son iguales si cada elemento de la primera es igual al elemento
Una secuencia slo es un conjunto ordenado de elementos. Una'secuenciaS se correspondiente de la segunda. Una subsecuencia es una porcin contigua de una se-
escribe, a veces, numerando sus,elemeros, por .ejemplo cuencia. Si Ses una secuencia, la funcin sub (S, i,j) se refiere a la subsecuencia des
que comienza en la posicin i en S y consiste enj elementos consecutivos. As, si Tes
S= < so, SI, .. , , S n- I > igual a sub (S, i, k) yT es la secuencia < t 0, t ,. ... , .. , lk-1 >, tenemos que t 0 = s;,
11 = si+,. ... , tk-l = si + k-1. . Sii no est entre Oy len(S) - k, entonces sub(S, i, k)
Si S contiene n elementos, se dice que S tiene longitud,n. Suponemos que existe se define como nilseq.
una funcin de longitud tal que es la longitud de la secuencia S. Suponemos tambin La concatenacin de. dos secuencias, escrita como S + T, es la secuencia que
que existe una funcin first (S), que regresa el valor del primer elemento de S (so en consiste en todos los elementos de S seguidos de todos los de T. A veces, es deseable
el ejemplo anterior) y una funcin !ast (S) que regresa el valor del ltimo elemento especificar la insercin de un elemento en medio de una secuencia. place(S, ;, x) se
de S (s,_ 1 en el ejemplo anterior). Hay una secuencia especial de longitud O; llamada define como la secuencia S con el elemento x insertado inmediata.mente despus de la
nilseq, que no contiene elementos. /ast (nilseq) y first (nilseq) quedan indefinidas. posicin i (o en el primer elemento de la secuencia si i es ';-l). Todos los elementos
Deseamos definir un ADT stp 1 cuyos valores sean secuencias de elementos. Si subsecuentes se recorren una posicin. Es decir, place(S, i, x) es igual a sub(S, O, i)
las secuencias pueden ser de longitud arbitrnria y constan de elementos que son to- + < x > + sub(S, i + 1, en(S) i).
dos del mismo tipo tp, entonces stp l puede definirse como sigue: La supresin de un elemento de una secuencia puede especificarse de dos ma-
neras. Si x es un elemento de la secuencia S, S - < x > representa la secuencia S sin
abstract typeaef <<tp>> stp1; todas las ocurrencias del elemento x. La secuencia de/ete(S, i) es igual a S con el ele-

18 Estructuras de datos en C Introduccin a la estructura de datos 19.


mento de la posicin i borrado. delete(S, i) puede tambin escribirse en trminos de rio inicial tiene la forma de una instruccin de asignacin en lenguaje C. Esto indica
tan slo que queremos definir el smbolo /astpos como representacin del valor de
otras operaciones como sub(S, O, i) + sub(S, i + 1, len(S) - i).
len(sl) - len(s2) para usarlo dentro de la postcondicin y simplificar la apariencia
Un ADT para cadenas de caracteres de longitud variable
de la condicin. Aqu, /astpos representa el mximo valor posible del resultado (esto
es, la ltima posicin de si en donde puede comenzar uasubcadena de longitud
Como ejemplo del uso de la notacin de secuencias en la definicin de un igual a la de s2). /astpos se usa dos veces dentro de la postcondicin misma.
ADT, desarrollamos la especificacin de un ADT para cadenas de caracteres de lon- Podramos haber escogido en ambos casos el uso de la expresin len(sl) - len(s2),
gitud variable. Existen cuatro operaciones bsicas (sin tomar en cuenta la asignacin que es ms grande, pero elegimos un smbolo ms pequeo (lastpos) para tener ms
y la igualdad) que se incluyen con frecuencia en los sistemas que mantienen tales claridad.
La postcondicin misma afirma que debe cumplirse una de dos condiciones.
cadenas:
Las dos condiciones, separadas por el operador 11 , son las siguientes:

/ength funcin que regresa la longitud actual de la cadena l. El valor de la funcin (pos) es -1 y s2 no aparece como subcadena de sl.
concat funcin que regresa la concatenacin de dos cadenas de entrada 2. El valor de la funcin se encuentra entre Oy lastpos, s2 aparece como subcade-
substr funcin que regresa una subcadena de una cadena dada na des I y comienza en la posicin del valor de l.a funcin; s2 no aparece como
pos funcin que regresa la primera posicin de una cadena como subcadena des l en ninguna posicin anterior. .
subcadena de otra
Observe el uso de un pseudolazo for en una condicin. La condicin
abstract typedef <<char>> STRING;

abstract length(s) for ( i = x; i <= y;. i++)


STRING s; (condition(i))
postcondition length == len(s);
es verdadera si condition(i) es verdadera para todo i desdex hasta y, inclusive. Tam-
abstract STRING concat(s1,s2) bin es verdadera si x >y.En otro caso, la condicin/or completa es falsa.
STRING s1,s2;
postcondition concat == sl + s2; Tipos de datos en lenguaje C
abstract STRING substr(s1,i,j)
STRING s1; El lenguaje C contiene cuatro tipos de datos bsicos: int, float, char y double.
int i,j; En la mayora de las computadoras estos cuatro tipos son nativos del hardware de la
precdndition o <= 1 < 1en(s1); mquina. Ya vimos cmo pueden implantarse en el hardware enteros, valores de
O<= j <= len(s1) - i; punto flotante y caracteres. Una variable double es un nmero de punto flotante de
postcondit1on substr == SLJ.b(s1, i , j ) ; doble precisin. Existen tres calificadores que pueden aplicarse a enteros: short (cor-
abstract pos(s1,s2) to), long (largo) y unsigned (sin signo). Una variable de entero short o long se refiere
STRING s1,s2; al tamao mximo del valor de la variable. El tamao mximo real que implican
postcondition llastpos = len(s1) - len(s2) *I short int, long int, o int vara de acuerdo con la mquina. Un entern sin signo es un
((pos== -1) && (for(i = o; entero que siempre es positivo y se rige por las leyes del mdulo 2", donde n es el
i <= rastpos; i++)
(s2 <> sub(s1,i,len(s2)))))
nmero de bits en un entero.
Una declaracin de variable en el lenguaje Chace dos especificaciones. Primera:
11 especifica cuanta memoria debe apartarse para objetos declarados con ese tipo. Por
( (pos >= O). && (pos <= lastpos)
&& (s2 == sub(str1,pos,len(s2)) ejemplo, una variable de tipo int debe tener suficiente espacio para el mayor valor
&& (for(i = 1; i < pos; i++) , posible de un entero. Segunda: especifica cmo han de interpretarse los datos repre-
(s2 <> sub(s1,i,len(s2))))); sentados por cadenas de bits. Los mismos bits en una localidad de memoria
especfica pueden interpretarse como entero o como nmero de punto flotante, lo
cual da lugar-a dos valores numricos por completo diferentes.
La post condicin para pos es compleja e introduce novedades en la notacin, Una declaracin de variable especifica que debe reservarse memoria para el ob-
por lo que la reconsideraremos aqui. Primero, ntese que el contenido del comenta- jeto del tipo especificado y que podemos referirnos a dicho objeto por medio del
identificador de variable especificado.

Estructuras de datos en C Introduccin a la estructura de datos 21


20

__ __
,, ,_,,, --"'
Apuntadores en lenguaje C doral entero que precede en forma inmediata a *pi, pi + 2 es el apuntador al segundo
entero que sigue a *pi y as sucesivamente. Por ejemplo, supngase que una m-
En realidad, el lenguaje C permite al programador referirse tanto a las locali- quina particular utiliza direccionamiento de byte, un entero requiere cuatro bytes y
dades como a los objetos (esto es, a los contenidos de localidades) mismos. Por el valor depi es 100 (es decir, si apunta al entero *pi en la localidad 100). Entonces el
ejemplo, si se declara ax como entero, &.x se refiere a la localidad que se ha reserva- valor de pi - 1 es 96, el de pi + 1 es 104 y el de pi + 2, 108. El valor de *(pi - 1) es
do para x. A &.x se le conoce como apuntador. el de los contenidos de los cuatro bytes 96, 97, 98 y 99 /nterpretados como enteros; el.
Es posible declarar una variable cuyo tipo de datos sean apuntador y cuyos va' de *(pi + 1) es el contenido de los bytes 104, 105, 106 y 107 interpretados como ente-
lores posibles sean localidades de memoria. Por ejemplo, las declaraciones ros; y el valor de *(pi + 2) es el entero que est en los bytes 108, 109, 110 y 111.
De manera parecida, si el valor de la variable pe es 100 (recurdese que pe es un
int *pi;
apuntador a char) y un carcter tiene un byte de longitud, pe ~ 1 refiere a la locali-
float *}?f; dad 99, pe + 1 a la 101, y pe + 2 a la 102. As, el resultado de la aritmtica de apun-
char *pe; tadores en el lenguaje C depende del tipo base del apuntador.
Observe tambin la diferencia entre *pi + 1, que refiere al 1 sumado al entero
*pi, y *(pi + 1) que refiere al entero que sigue al de la localidad pi.
1 declaran tres variables de tipo apuntador: pi apunta a un entero, p/apunta a.un n-
1 Un rea en la cual. desempean un papel prominente los apuntadores en e len-
r, mero de punto flotante y pe apunta a un .c.arcter. El asterisco indica qte lsvalores
guaje Ces en la trasmisin de parmetros a funciones. Por lo comn, Jos parmetros
de las variables declaradas son apuntadores de los tipos especificados en la declara-
1 se trasmiten por valor a una funcin en el lenguaje C, es decir, se copian los valores
r cin ms que objetos de ese tipo.
trasmitidos en los parmetros de la funcin llamada en el momento en que se invoca.
En muchos aspectos, un apuntador es similar a cualquier otro tipo de dato en
Si cambia el valor de un parmetro dentro de la funcin,. no cambia su valor en el
lenguaje C. El valor de un apuntador es una localidad de memoria de la misma ma-
programa que la llama. Por ejemplo, considrese el siguiente fragmento y funcin
nera que el valor de un entero es un nmero. Los valores del apuntador pueden asig-
de programa (el nmero de lnea solamente es una gua):
narse como cualesquiera otros. Por ejemplo, la instruccin pi = &x asigna el apun-
tador del entero x a la variable apuntador pi. 1 X = s;
La notacin *pi en lenguaje: C alude. al entero en la localidad referida por el 2 printf ( 11 %d \n", X) ;
apuntador pi. La instruccin x = *pi asigna el valor de este entero a la variable de 3 funct(x);
entero x. ,; printf'( 11 %a \nfl, X) ;
Note que el lenguaje C recalca que una declaracin de un apuntador especifica
el tipo de datos al cual. apunta. En las declaraciones anteriores, cada una de.las va- 5 funct(y)
riables pi, pfy pe son apuntadores a un tipo de datos espe:fico: int, float y ehar, 6 int y;
respectivamente. El tip de p[ no es, simplemente,_ un ''apu,t1ta~9r''' sino un ''ap,un- 7 {
tador '! un entero"_. De hecho., los tipos de piy pjson diferentes: pi es un apuntador 8 ++y;
a un. entero y pf es un apuntador a un nmero de punto flotante. Cada tipo dt de 9 printf( 11 %d\n 11 , y) ;
datos en el lenguaje C genera otro tipo de datos, pdt, llamado "apuntador a dt". 10 I* fin de /une! *I
Llamamos a dt el tipo base de pdt.
La conversin de pf del tipo "apuntador a un nmero de punto flotante" al ti- La lnea 2 imprime 5 y despus 3 llama ajunct. El vaor dex, que es 5, se copia eny y
po "apuntador a un nmero .entero" puede hacer.se al escribir comienza la ejecucin defunct. Despus, la lnea nueve imprime 6 y regresa/une/.
Sin embargo, cuando la lnea 8 incrementa .el valor de y; el valor de. x permanec;e
invariable. As, la lnea 4 imprime 5; x y y refieren a dos variables diferentes que suce-
pi = (int *) pf; de que tienen el mismo valor al principio defunct. y puede cambiar independiente-
mente de x.
donde el prefijo (int*) convierte el valor de pf al tipo "apuntador 'a un int" o Si deseamos usar funct para modificar el valor de x, debemo.s trasmitir l.a direc-
Hin(*". cin de x de la siguiente manera:
La importancia de cada apuntador asociado con un tipo de datos particular se
aclara cuando revisamos las.herramientas aritmticas que brinda el lenguaje C para 1 X = 5;

los apuntadores. Si pi es un apuntador a un entero, ento~ces pi + 1 es el apuntador 2 printf.( 11 %d \n 11 , X) i


3 funct(&x);
al entero que sigue inmediatamente al entero *pi en la memoria, pi - 1 es el apunta- ,; printf( 11 %d.\n 11 , X) ;

22 Estructuras de datos en. O Introduccin a la estructura de datos 23


en el lenguaje C puede usar simplemente las estructuras de datos tal y como estn de-
S funct(py) finidas en el lenguaje sin importarle la mayora de esos detalles de implantacin).
6 int *py;
En lo que resta del libro, desarrollamos estructuras de datos ms complejas y
7 1
8 ++(*PY);
mostramos su utilidad en la solucin de problemas. Mostramos tambin cmo
q printf(%d\n 11 , *py); implantar dichas estructuras usando las que ya estn disponibles en el lenguaje C.
1,0 / * fin de funct *I Como los problemas que aparecen mientras intentamos implantar estructuras de da-
tos de alto nivel son bastante complejos, esto nos permitir tambin investigar con
ms profundidad el lenguaje C y obtener gran experiencia en su uso.
La lnea 2 imprime nuevamente 5 y la lnea 3 llama afunct. Ahora, sin embargo, el A menudo, no existe una implantacin de software ni de hardware, que pueda
valor transferido no es el valor entero de x sino el valor apuntador &x. Este es la di- modelar un concepto matemtico por completo. Por ejemplo, es imposible represen-
reccin de x. El parmetro defunct no es ms y de tipo int, sino py de tipo int. (Es tar nmeros enteros arbitrariamente grandes en una computadora, debido a que
conveniente nombrar a las variables apuntadores comenzando con la letra p para el tamao de la memoria es finito. En consecuencia, no es el tipo'de datos "entero"
recordar tanto al programador como al lector del programa que se trata de un apun- el que representa el hardware sino el tipo "entero entre x y y", donde x y y son los
tador. Sin embargo, no es un requisito del lenguaje C, as que hubiramos podido enteros ms pequeo y ms grande, respectivamente, representables por la mquina.
llamar y al parmetro apuntador.) La lnea 8 ahora aumenta al entero en la localidad Es importante reconocer las limitaciones de una implantacin particular. A
py; py, sin embargo, no cambia y conserva su valor inicial &x. As, py apunta al en-
menudo, ser posible representar varias implantaciones de un mismo tipo de datos,
tero x de tal manera que cuando aumenta *py, aumenta x. La lnea 9 imprime 6 y cada una con su fuerza y debilidad. Una implantacin especfica puede ser mejor
cuando regresa/une/, la linea 4 imprime tambin 6. Los apuntadores son el mecanis- que otra para una aplicacin particular y e( programador tiene que estar enterado de
mo usdo en el lenguaje C para permitir a una funcin llamada modificar las
las posibles ventajas y desventajas que conlleva.
variables de la funcin que llama. Una consideracin importante en cualquier implantacin es su eficiencia. En
realidad, la razn por la cual las estructuras de datos de alto nivel que analizamos
Estructuras de datos en el lenguaje C aqu no estn construidas en el lenguaje C se debe a la sobrecarga significativa que
acarrearan. Hay lenguajes de bastante ms alto nivel que el lenguaje C que tienen
Un programador en lenguaje C puede pensar que est lenguaje define una m- incluidos estos tipos de datos, pero muchos son ineficientes, por lo que no se ha
quina nueva con sus propias capacidades, tipos de dats y operaciones. El usuario
difundido su empleo.
puede plantear una solucin a un problema en trminos de las ms tiles construc- La eficiencia se mide por lo com(m mediante dos factores: espacio y tiempo. Si
ciones en el lenguaje C en vez de hacerlo en trminos de las construcciones de len- una aplicacin particular depende demasiado de la manipulacin de estructuras de
guajes de mquina de bajo nivel. Por lo tanto, pueden resolverse los problemas con datos de alto nivel, la velocidad a la cual pueden ejecutarse, ser el factor determi-
mayor facilidad debido a que hay un amplio conjunto de herramientas disponibles. nante en la velocidad de toda la aplicacin. De manera parecida, si un programa uti-
El estudio de las estructuras de datos implica, en consecuencia, dos metas liza un gran nmero de estas estructuras, una implantacin que use una cantidad
complementarias. !.,a primera es la de identificar y desarrollar las entidades y opera- excesiva de espacio para representarlas ser impracticable. Por desgracia, hay por lo
ciones matemticas ms tiles y determinar qu clase de problemas pueden resolverse general un compromiso entre .las dos eficiencias, de manera que una implantacin
m.ediante ellas. La segunda es la de determinar las representaciones para esas entida- que es rpida usa ms memoria que una lenta. La eleccin de la implantacin, en tal
des abstractas y para implantar las operaciones con esas representaciones concretas. caso, implica una cuidadosa evaluacin de las ventajas y desventajas entre las diver-
La primera, concibe a los tipos de datos de alto nivel como una herramienta que
sas posibilidades.
puede usarse para resolver otros problemas; la segunda concibe la implantacin de
un tipo de datos como un problema a resolver por medio de los tipos de datos ya
existentes. Al determinar las representaciones para las entidades abstractas, debemos
ser cuidadosos para especificar cules herramientas tenemos para la construccin. EJERCICIOS
Por ejemplo, debe establecerse si disponemos del lenguaje C en su totalidad o si estamos
1.1. t. En el libro se hace una analoga entre la longitud de una recta y el nmero de bits de in-
restringidos a las herramientas del hardware de una mquina en particular.
formacin en una ca~ena de bits. En qu sentido es inadecuada dicha analoga?
En las prximas dos secciones de este captulo estudiamos varias estructurasele
l. 1.2. Determine qu tipos de datos del hardware estn disponibles en la computadora de su
datos que ya existen en el lenguaje C: el arreglo y la estructura. Describimos las
instalacin particular y qu oper_aciones pueden ejecutarse con ellos.
herramientas disponibles en el lenguaje C para utilizar esas estructuras. Tambin
l. 1.3. Pruebe que hay" 2h maneras diferentes de configurar n conmutadores (que tienen slo
nos enfocamos a la definicin abstracta de esas estructuras de datos y cmo pueden
dos posiciones po.sibles). Suponga que queremos tener m configuraciones .. Cuntos
ser tiles para la solucin de problemas. Finalmente, examinamos cmo pueden
conmutadores seran necesarios?
implantarse si no estuvieran disponibles en el lenguaje C. (Aunque un programador

Introduccin a la estructura de datos 25


24 Estructuras de datos en C
1.1.4. Interprete las siguientes combinaciones de bits como enteros binarios positivos, como dispuestos de tal manera que hay un elemento cero, un elemento primero, uno se-
enteros binarios en notacin de complementos a 2 y como enteros decimales codifica- gundo, un tercero y as sucesivamente. Por "homogneo", entendemos que todos
dos en binario. Si una combinacin no puede interpretarse como un entero decimal los elementos del arreglo son del mismo tipo. Por ejemplo, un arreglo puede conte-
codificado en binario, explique la razn. ner elementos enteros o caracteres, pero no ambos.
(a) 10011001 Sin embargo, la especificacin de la forma de una estructura de datos no per-
(b) 1001 mite describir por completo la estructura. Tenemos que explicar tambin cmo se
(e) 000100010001
tiene acceso a la misma. Por ejemplo, en lenguaje C, la declaracin
(d) OII!Olll
(e) 01010101 int a(100l;
(f) 100000010101
t.1.5. Escriba las funciones en lenguaje C add, substract, y multiply, que lean dos cadenas de especifica un arreglo de !00 enteros. Las dos operaciones bsicas que admiten un
Oy 1 que representan enteros binarios no negativos' ' imprima la cadena repreSentando arreglo son la extraccin y almacenamiento. La extraccin es una funcin que aep-
s suma, su diferencia y su producto, respe~tivamerte. ta un arreglo a y un ndice i y regresa un elemento del arreglo. En lenguaje C, el
1.1.6', Suponga qu'e en una computdora ierriria la unidad- bsi~a d meinoria es un_- trt resultado de esta operacin se denota por a[i]. El almacenamiento acepta un arreglo a,
(dgito t'rnario) eri lgar de un bit. Un tritpuede tener tres posibles'posiciones (O, 1 y 2) un ndice i y un element.o x. En lenguaje Cesta operacin se denota pormedio de la
en !ugr de dos (0 y l ). Muestre cmo peden epresentarse enteros negativos en nota- instruccin de asignacin a[i] = x. Las operaciones se definen por la regla segn
cin ternaria us.ndo tales trits por un mtodo anlogo al de la notacin binaria usando la cual despus de ejecutada la instruccin de asignacin anterior, el valor de a[i] es
;'
bits-. Hay algn entero no negativo que pUeda representarse usando notacin ternaria x. Antes de que se haya asignado un valor a un elemento del arreglo, su valor est
y trits pero que no se' pueda usando ntaciri binaria y bits? Hay alguno con el que indefinido y no est permitido referirnos a l en una expresin.
ocurra lo contrario? Por qu SOf;l ms comunes las computadoras binarias que las
El ndice ms pequeo de un arreglo se le conoce como su lmite inferior, el
ternarias?
cual en el lenguaje C siempre es O, y el ms grande se conoce como lmite superior. Si
1. 1..7. Escriba un programa en lenguaje C para leer una cadena de O y 1 que representen un
lmite I es el lmite inferior de un arreglo y lmite 2 el superior, el nmero de elemen-
entero positivo en binario. e imprima una cadena de O, I y 2 que represente el mi$rnO
tos del arreglo, llamado su rango, est dado por lmite 2 - lmite 1 + l. Por
nmero en notacin ternaria (vase el ejercido anter,ior). EScriba otro programa en len-
guaje C para leer un nmero ternario y escribir Sll equivalente en binario. ejemplo, en el arreglo a, declarado antes, el lmite inferior es O, el superior 99 y el
rango es 100.
1.1.8. Escriba una especificacin de ADT para nmeros complejos a + bi, d_onde abs(a + bt)
es sqrt(a 2 + b 2), (a + bi) + (e + di) es (a.+ e) + (b + d)i, (a + bi) * (e + di) es Una caracterstica importante de los arreglos en lenguaje C es que ni el lmite
(a* e b * d) + (a* d + b c)i, y -(a + bi) es (,-a) + (-b)i .. superior ni el inferior (y, por tanto, tampoco el rango) pueden cambiarse durante la
ejecucin de un programa. El lmite superior siempre se fija en Oy el superior se fija
en el momento en que se escribe el programa.
1.2. ARREGLOS EN LENGUAJE C Una tcnica muy til es la de declarar un lmite como identificador constante,
de tal manera que se minimiza el trabajo requerido para modificar el. tamao de un
En sta y la siguiente seccin examinamos varias estructuras de datos que son p.irte arreglo. Por ejemplo, considrese el siguiente fragmento de programa parndeclarar
valiosa del lenguaje C. Veremos cmo usar estas estructuras y cmo hnplantarlas. e inicializar un arreglo:
Esas estructuras son tipos de datos compuestos o estructurados; es decir, estn con-
formadas por estructuras de datos'ms simples que existen en el lenguaje. El estudio
int a(1DDJ;
de ese tipo de estructuras de datos trae consigo un anlisis de cmo combinar estruc- for(i = D; i < 100; a[i++J = O);
turas simples para formar complejas y cmo extraer un componente especfico de las
estructuras compuestas. Suponemos que el lector haya visto ya esas estructuras de
Para cambiar el tamao del arreglo por uno menor (o mayor), debe cambiarse la
datos en un curso introductorio de programacin en lenguaje C y que sep c6mci se constante 100 en dos lugares; una en las declaraciones y otra en la instruccin ]or.
definen y usan en el lenguaje C. Por consiguiente, no haremos hincapi, en estas sec- Considrese la siguiente opcin equivalente:
ciones, en los diversos detalles asociados con esas estructuras, sino que destacremos
aquellas caractersticas interesantes'desde el punto de vista de la estructura de datos. #dffine NUMELTS 100
El primero de estos tipos de datos es el arreglo. La forma.ms simple de arreglo 'int'a(NUMELTS];
es un arreglo unidimensional que puede definirse de manera abstracta como un con- for(i = O; .i < NUMELTS; a(i++] = O);
junto finito ordenado de elementos homogneos. Por "finito" entendemos que'hay
un nmero especfico de elementos en el arreglo; nmero que puede ser grande o Ahora, slo precisamos un cambio nico en la definicin de la constnle para cam-
pequeo, pero debe existir. Por "ordenado" entendemos que los elementos estn biar el lmite superior.

26 Estructuras de dE\toS' eriC Introduccin.a la estructura de datos 27.


El arreglo como ADT Uso de arreglos unidimensionales

Podemos representar un arreglo como un tipo de datos abstracto al ampliar un Cuando es necesario guardar un gran nmero de objetos en la memoria y refe-
poco las convenciones y la notacin discutidas antes. Suponemos que existe la fun- rirnos a todos de la misma manera usamos un arreglo unidimensional. Veamos
cin type(arg), que regresa el tipo de su argumento arg. Claro que tal funcin no cmo se aplican estos dos requerimientos a situaciones prcticas.
puede existir en el lenguaje C, puesto que ste no puede determinar de manera din- Supngase que queremos leer 100 enteros, encontrar su promedio y determinar
mica el tipo de una variable. Sin embargo, como aqu no nos importa la implanta- cunto se desva cada entero de e.se promedio. El siguiente programa lo hace:
cin sino la especificacin, podemos usar tal funcin.
Digamos que ARRTYPE(ub, eltype) denota el ADT correspondiente al #define NUMELTS 100
arreglo del lenguaje del tipo eltype array[ub]. Este es nuestro primer ejemplo de un a ver()
ADT con parmetros, en el cual el ADT preciso est determinado por los. valores de {
int nurn[NUMELTS]; I* arreglo de nmeros *I
[ uno o ms parmetros. En este caso, ub y eltype son los parmetros; ntese que elty-
i-nt i;
pe es un indicador de tipo, no un valor. Podemos considerar ahora cualquier arreglo suma de los nmeros
1 int total; I* *I
[, unidimensional como una entidad de tipo ARRTYPE. Por ejemplo, ARRTYPE 'float avg;, I* promedio de los n'meroS *I
(10, nt) representara el tipo del arreglo x en la declaracin int x[lO]. La especifica- float diff; I* diferencia el.fre cada *I
f
cin es la siguiente: I* nmero y el promedio *I
l abstract typedef <<eltype, ub>> ARRTYPE(ub, eltype);
total= O;
far (i = O; i < NUMELTS; i++)
I* leer el nmero en el arreglo y sumarlo *I
condition type( ub) == int'; scanf( 11 %d' 1 ,&num[i]);
total+= num[i];
abstract eltype extract(i;i) /* escrito- como a [ i ] *I
l* fin de flor*!
ARRTYPE(ub, eltype) a; avg = total / NUMELTS; I* clculodelpromdio *I
int i; printf ("/indiferencia del Ilmero") / * imprime encabezado * I
precondition O<= i < ub; I * imprime cada nmero y su diferencia *I
postcondition extract == i
abstract store(a, i, elt) ! * eScrito como a [iJ el! *I far (i = O; i < NUMELTS; i++)
ARRTYPE (Ub, eltype) a; diff = num[iJ - avg;
printf( 11 \n %d %d 11 , numCiJ, diff);
int i;
el type el t ) I* fin de for *I
printf("/el promedio es: %d", avg)
precondition o <= i < ub;
p,:istcondi tion a[il == elt; I * fin de aver * I

Este programa utiliza dos grupos de 100 nmeros. El primero es el conjunto de


La operacin store es nuestro primer ejemplo de una operacin que modifica los enteros de entrada y est representado por el arreglo num', el segundo es el con-
uno de sus parmetros; en este caso, el arreglo a. En la postcondicin se indica al es- junto de las diferencias, resultado de la asignacin sucesiva de valores a la variable
pecificar el valor del elemento del arreglo al cual se ha asignado elt. Suponemos que diff en el segundo ciclo. Surge la pregunta: por qu se usa un arreglo para guardar
todos los parmetros conservan el mismo valor despus de aplicada la operacin en todos los valores del primer grupo simultneamente, mientras que slo se usa una
una postcondicin, como antes, a menos que se especifique un valor modificado en nica variable por vez para guardar un valor del segundo grupo?
la postcondicin. No es necesario especificar que no cambia ninguno de. los valores La respuesta es sencilla. Cada diferencia s computa e imprime sin que se nece-
de este tipo. As, en este ejemplo no cambia el valor de ningn elemento del arreglo, site otra vez. De modo que la variable dff puede volverse a usar para la diferencia
excepto el elemento al que se le asign el/. del entero siguiente y su promedio. Sin embargo, todos los enteros originales, que
Ntese que una vez definida la operacin extrae/, junto con su notacin de son los valores del arreglo m11n, tienen que guardarse en memoria. Aunque cada uno
corchetes a[], puede usarse esta notacin en la postcondicin para especificar des- puede sumarse a total como entrada, se tiene que retener hasta despus de que se ha
pus la operacin store. Sin embargo, dentro de la postcondicin de extrae/, tiene calculado elpromed}o para que lo use el programa que calcula la diferenci entre el
que usarse la notacin de secuencias subindizadas, pues estamos definiendo la nota- valor original y. el promedio. Por esto se usa un arreglo.
cin de corchetes para arreglos. Claro; podran haber usado 100 variables por separado para retener los ente-
ros. La ventaja de un arreglo, sin embargo, es que permite al programador declarar

28 Estructuras de datos en C Introduccin a la estructura de datos 29


solamente un identificador y obtener as mucho espacio. Adems, junto con el ciclo elemento b[O] nos referimos a la localidad base(b); cuando lo hacemos a b[l] lo ha-
FOR, permite al programador tambin referirse a cada elemento del grupo de una cemos al elemento.en base(b) + esize; cuando es a b[2] lo hacemos al elemento en
manera uniforme en lugar de obligarlo a codificar la instruccin de la siguiente base b + esize * esize * 2. En general, referirse a b [11 remite al elemento en la locali-
dad base(b) + i * esize. As que es posible referirse a cualquier elemento en el
manera:
arreglo, dado su ndice.
scanf(' 1%d%d%d ... %d 11 , &numo, &num1, &num2, ... , &numqq); En realidad, en el lenguaje C, un arreglo variable se implanta como un apunta-
dor variable. El tipo de la variable ben la declaracin anterior es "apuntador a un
Un elemento particular del arreglo puede recuperarse directamente por medio entero" o int *. No aparece asterisco en la declaracin pues los corchetes sealan de
de su ndice. Por ejemplo, supngase que una compaa usa un programa en el cual modo inmediato que la variable es un apuntador. La diferencia entre las declara-
se declara un arreglo por medio de: ciones int *be int. b[IOO], es que la ultima reserva tambin 100 localidades enteras
comenzando desde la localidad b. En el lenguaje C, el valor de la variable b es
int sales[1DJ; base(b), y el valor de la variable b[i], donde i es un entero, es *(b + i). Recurdese,
El arreglo contendr los precios de venta por un periodo de diez aos. Supngase de la seccin !, que como bes un apuntador a un entero, *(b + i) es el valor del
que cada lnea de entrada del programa contiene un entero entre O y 9, el. cual repre- i-simo entero siguiente al de la localidad b. b[i], .el elemento de la localidad base(b)
senta tanto el ao como el precio de venta para ese ao y que se desea leer el precio + i * esize, es equivalente al elemento apuntado por b + i, que es *(b + i).
de venta en el elemento apropiado del arreglo. Podemos hacerlo al ejecutar la ins- En el lenguaje. C todos los elementos de un arreglo tienen el mismo tamao fijo
truccin y predeterminado. Algunos lenguajes de programacin,. sin embargo, permiten
arreglos de objetos de tamaos diferentes. Por ejemplo, un lenguaje puede permitir
scanf('l%d%d'', &yr., &sales[yr]); arreglos de cadenas de caracteres de longitucl variable. En tales casos, no puede usar-
se el mtodo anterior para implantar el arreglo, lo cual se debe a que el mtodo de
dentro de un ciclo. En esta instruccin, un elemento particular del arreglo se admite clculo de la direccin de un elemento especfico del arreglo depende del conoci-
directamente mediante su ndice. Considrese la situacin cuando se han declarado miento del tamao fijo de cada elemento precedente (esize). Si no todos los elemen-
diez variables sO, si, ...... , s9. As despus de ejecutar scanf("'lod", &yr), la canti- tos tienen el mismo tamao, debe usarse una implantacin diferente.
dad de venta no puede leerse en la variable apropiada ni para establecer yr como el Un mtodo para implantar un arreglo de elementos de tamao variable es
entero que representa el ao, sin codificar algo parecido a lo siguiente: reservar un conjunto contiguo de localidades de memoria, cada una de las cuales con-
serva una direccin. El contenido de cada una de esas localidades es la direccin del
switch(yr) 1 elemento del arreglo de longitud variable en alguna otra porcin de la memoria. Por
case O: scanf("%d 11 , &sO); ejemplo, la figura 1.2. la muestra un arreglo de cinco cadenas de caracteres de longi-
case 1: scanf( 1'%d 11 ~ &s1); tud variable implantado segun las dos maneras presentadas en la seccin l. l. para
enteros de longitud variable: Las flechas indican las direcciones de otras porciones
de memoria. El carcter 'b' indica un espacio en blanco. (Sin embargo, en el len-
guaje C, las propias cadenas se implantan como arreglos, de tal manera que un
case q: scanf( 11 %d''J &s9); arregl de cadenas en realidad es un arreglo de arreglos: un arreglo bidimensional,
I * fin de switch * l
. no unidimensional.) .
Como la longitud de cada direccin es fija, la localidad de la direccin de un
Lo .cual es desastroso con 10 elementos: imagnese la inconveniencia si hubiera cien-
elemento particular puede calcularse de la misma manera que tin elemento de longi-
tos o miles de elementos.
tud fija, como en los ejemplos anteriores. Una vez que se conoce dicha localidad, su
contenido puede usarse para determinar la ,localidad del elemento real del arreglo.
Implantacin de arreglos unidimensionales
Esto aade, desde luego, un nivel extra de direccionamiento indirecto al referirnos
a un elemento del arreglo al incluir una referencia extra de memoria, lo que dismi-
Un arreglo unidimensional puede implantarse fcilmente. La dedaracin
nuye la eficiencia. Sin embargo, es el pequeo precio que debemos pagar por la
int b[1DDJ.; conveniencia de poder mantener tal arreglo.
Un mtodo similar para implantar un arreglo de elementos de.tamao variable
en lenguaje C, reserva IOO localidades sucesivas de memoria, cada una lo bastante es conservar todas ls porciones de longitud fija de los elementos en un rea conti-
grande para contener un solo entero. La direccin de la primera de esas localidades gua del arreglo, adems de conservar las direcciones de la porcin de longitud
se conoce como dfreecin base del arreglo by se denota por base (b). Supngase que variable en el rea contigua. Por ejemplo, en la implantacin de cadenas de caracteres
el tamao de cada elemento individual del arreglo es esize. Entonces, al referirnos al de longitud variable presentada en la seccin previa, cada cadena contiene una por-

Estructuras de datos en C Introduccin a la estructura de datos 31


30
r
1

SHELLO 5

10

L-----
...J---1 I I I I I 1 I I 1
G O O D b N 1 G H T

8 C O M P U T E R 1----------1-t---l I IMI I I I IR 1
10 1
C O p U T E

2 A T

(b)

Figura 1.2.1 (cont.) Jmpfantaciones de un arreglo de cadenas de longitud variable.

cin de longitud fija (un campo de, un byte de largo) y una porcin de longitud
variable (la propia cadena de caracteres). Una implantacin de un arreglo de cadenas
de caracteres de longitud variable conserva el largo de la cadena junto con la direc-
cin tal y como se muestra en la figura 1.2.1 b. La ventaja de este mtodo es que
pueden examinarse las partes de un elemento que son de longitud fija sin hacer refe-
rencia adicional a la memoria. Por ejemplo, una funcin para determinar la longi-
tud actual de una cadena de caracteres de longitud variable puede implantarse con
una sola referencia a la memoria. A la informacin de longitud fija para un elemen-
to del arreglo de longitud variable que est guardada en el rea de memoria contigua
C O M P U T E R \O
al arreglo se le conoce, con frecuencia, como encabezado.

Arreglos como parmetros

1NI O I I I I I I I I l\ol
B B B B B B B B
Todos los parmetros de una funcin en el lenguaje C tienen que declararse
dentro de la funcin. Sin embargo, el rango de un arreglo unidimensional como
parmetro se especifica slo en el programa principal, pues en el lenguaje C no se
asigna memoria nueva a un arreglo como parmetro. Ms bien los parmetros hacen
referencia al arreglo original que fue asignado en el programa que llama la funcin.
(a) Por ejemplo, considrese la siguiente funcin para calcular el promedio de los ele-
mentos de un arreglo:
Figura 1.2.1 Implantaciones de un arreglo de cadenas de longitud variable.

32 Estructuras de datos eli C Introduccin a la estructura de datos 33


float avg(a, size) Cadenas de caracteres en el lenguaje C
float a[J; I* no se indica rango *!
int size; Una cadena se define, en el lenguaje C, como un arreglo de caracteres. Cada
{ cadena termina con el carcter NULL (nulo), que indica su final. Se denota una ca-
int i;
dena constante mediante cualquier conjunto de caracteres entre dobles comillas. El
float sum;
carcter NULL se incluye en forma automtica al final de una cadena de caracteres
sum = O; constante cuando se almacena. Dentro de un programa, el carcter NULL se repre-
for (i=D; i < size; i++) senta por la secuencia de escape \ O. Otras secuencias de escape que pueden usarse
sum+=a(iJ; son \ n, para el carcter de lnea nueva, \ t para el carcter tab, \ b para el espacio
return(sum / size); de retroceso, \ "para las comillas dobles, \ \ para la antidiagonal \, \ 'para las
I* ndeavg.*I comillas simples, ,r para el carcter de retorno de carro y \ f para el carcter avance
de hoja.
En el programa principal, habramos escrito Una cadena constante representa un arreglo cuyo lmite inferior es O y cuyo
lmite superior es el nmero de caracteres de la cadena. Por ejemplo, la cadena
#define ARANGE 100 "HOLA TERE" es un arreglo de 10 caracteres (el espacio en blaco y \ O cuentan
float a[ARANGEJ;
cada uno como un carcter) y "no c.onozco el Charlie \' s" es un arreglo de 24 ca-
avg(a, ARANGE);
racteres (la secuencia de escape\' representa la comilla simple).

Ntese que si se necesita en la funcin l rango del arreglo, tiene que transferirse por Operaciones con cadenas de caracteres
separado. .
Como una variable arreglo en el lenguaje C es un apuntador, los parmetros Presentemos funciones en lenguaje C para implantar algunas operaciones pri-
del arreglo se transfieren por referencia y no por valor. Esto es, a la inversa que con mitivas con cadenas de caracteres. Suponemos las siguientes declaraciones para
las variables nicas transferidas por valor, los contenidos de un arreglo no se copian todas esas funciones
cuando se transfiere como parmetro en el lenguaje C.
En su lugar, se transfiere la direccn base del arreglo. Si una funcin que lla- #define STRSIZE 80
ma. el programa contiene la llamada funct(a), donde a es un arregl y la funcin char string[STRSIZE];
funct tiene el encabezado

funct(b)
La primera funcin encuentra la longitud actual de una cadena.
intb[J;
strlen(string)
la instruccin char string[J;
{
int i;
b[il - x;
for (i=D; string[iJ != '\0 1 ; i++)
dentro de funct modifica el valor de a[i] en el interior de la funcin que llama.
Dentro defunct, b se refiere al mismo arreglo de localidades que a en la funcin que return(i);
llama. !* fin de strlen *!
Transferir un arreglo por referencia y no por valor es ms eficiente en cuanto.a
espacio y a tiempo. Se elimina el tiempo requerido para copiar un arreglo entero La segunda funcin acepta como parmetros dos cadenas. La funcin regresa
cuando se llama una funcin. Se reduce tambin el espacio necesario para Una se- un entero que indica la posicin inicial de la primera ocurrencia del segundo par-
gunda copia del arreglo en la funcin llamada, siendo slo necesario contar cori el metro de la cadena parmetro dentro del primero de la primer cadena. Si la segunda
espacio para una variable apuntador. no existe dentro de la primera, el resultado es -l.

34 Estructuras de datos en C Introduccin a la estructura de datos 35


r
.
.
. stcpos(s1, s2) Lo anterior define un nuevo arreglo que contiene tres elementos, cada uno de
.
char sL(J, s2(J; los cuales es en si mismo un arreglo que contiene cinco enteros. La fig. 1.2.2 ilustra
dicho arreglo. Un elemento de es.e arreglo se obtiene especificando dos ndices: un
int len1, len2;
nmero de rengln y un nmero de columna. Por ejemplo, el elemento sombreado
int i, j1, j2;
en la fig. 1.2.2 est en el rengln 1 y en la columna 3 y podemos referirnos a l como
len1 = strlen(s1); a[J][3]. Un arreglo de este tipo se llama arreglo bidimensional. Al nmero de renglo-
len2 = strlen(s2); nes o de columnas se conoce como el rango de la dimensin. En el arreglo a, el rango
for (i=D; i+len2 <= len1; i++) de la primera dimensin es 3 y el de la segunda es 5. Por lo que el arreglo a tiene 3
for (j1=i, j2=0; j2 <= len2 && s1lj1l S2[j2J; renglones y 5 columnas.
j1++, j2++)
Un arreglo bidimensional ilustra con claridad las diferencias entre el punto de
if (j2 == len2) vista lgico y fsico de los datos. Un arreglo bidimensional es una estructura de datos
return(i);
lgica que es muy til en la programacin y en la resolucin de problemas. Por
retu.rn(-1);
ejemplo, tal arreglo es til para describir un objeto fsicamente bidimensional, como
! * .fin de strpos *!
un mapa o un tablero. Tambin es til para organizar un conjunto de valores que de-
Otra operacin comn con cadenas es la concatenacin. El resujtado de conca- pende de dos entradas. Por ejemplo, un programa para una tienda de departamen-
tenar dos cadenas consiste en los caracteres de la primera s'eguidos de los de la segun- tos que tiene 20 sucursales, cada una de las cuales vende 30 artculos, podra incluir
da. La siguiente funcin asigna a si el resultado de la concatenacin de si y s2. un arreglo bidimensional declarado por

strcat(s1, s2) int sales[20][3Dl;


char sL[J, s2[J;
Cada elemento sa/es[i]U] representa la cantidad de artculos j vendidos en la sucur-
int i, j ;
sal i.
f'or (i=D; s1[il != ' \o .. ; i++) Sin embargo, aunque para el programador es conveniente concebir los elementos
de un arreglo organizados en una tabla bidimensional (los lenguajes de programa-
for (j=D; S2[j] l= ' \O, ; s1[i++l s2[j++l) cin incluyen en verdad herramientas para tratarlas como un arreglo bidimensio-
nal), el hardware de muchas computadoras no tine tales herramientas. Un arreglo
/* fin de sfrcat *I debe almacenarse en la memoria de una computadora, la cual, por lo regular, es li-
neal; esto es, la memoria de una computadora es, en esencia, uri arreglo unidimen-
La ltima operacin con cadenas que presentamos es la operacin subcadena.
sional. Una sola direccin (que puede concebirse como el subndice de un arreglo
substr(sl, i, j, s2) asigna a s2j caracteres comenzando en sl[i].
unidimensional) se usa para localizar una localidad particular de la memoria. Para
substr(s1, i, j, s2)
implantar un arreglo bidimensional, es necesario desarrollar un mtodo para orde-
char s1(], s2[J; nar estos elementos en forma lineal y para transformar una referencia bidimensional
inti,j; en representacin lineal.
Un mtodo para representar en la memoria un arreglo bidimensional es la
int k, m; representacin mediante rengln-mayor. Con esta representacin, el primer rengln
del arreglo ocupa el primer conjunto de localidades reservado para el arreglo, el se-
for(k:::;i,m O; m < j ; s2[m++J s1[k++l) gundo ocupa el siguiente conjunto y as sucesivamente. Puede haber tambin algu-
nas localidades al principio del arreglo fsico como encabezado, las cuales corttienen
S2[m]='ID';
los lmites superior e inferior de las dos dimensiones.' (Este encabezado no debe con-
! * fin de substr * I
fundirse con los discutidos antes. Este es del arreglo completo mientras que los men-
Arreglos bidimensionales cionados antes eran de los elementos individuales del arreglo). La figura 1.2.3
muestra la representacin mediante rengln-mayor del arreglo bidimensional a
El tipo de componente de un arreglo puede ser otro arreglo. Por ejemplo, declarado arriba y mostrado en la figura 1.2.2. Adems, el encabezado no debe ser
podemos definir: contiguo a los elementos del arreglo, aunque podra en su lugar contener la direccin
del primer elemento del mismo. Tambin, si los elementos del arreglo bidimensional
int a[3][5J; son objetos de longitud variable, los propios elementos del rea contigua pueden

36 Estructuras de datos en C Introduccin a la estructura de datos 37


1

de tal manera que la direccin del primer elemento del rengln i J est en base(ar) +
columna columna columna columna columna l * r2 * esize. Por consiguiente, la direccin de ar[il][i2] est en ,
O 1 2 J 4
base( ar) + ( i1 * r2 + i2) * esize
rengln O
Como ejemplo, considrese el arreglo a de la figura 1.2.2, cuya representacin
rengln l
se muestra en la figura 1.2.3. En este arreglo, rl = 3, r2 = 5 y base(a) es la direc-
cin de a[O][O]. Supongamos tambin que cada elemento del arreglo requiere una
sola unidad de memoria, de tal manera que esize es igual a l. (Esto no es necesa-
Figura 1.2.2 Arreglos bidimensionales.
riamente cierto, dado que declaramos a como arreglo de enteros y un entero puede
necesitar ms de una unidad de memoria en una mquina particular. Sin embargo,
aceptamos la suposicin por simplicidad). Entonces la localidad de a[2][4] puede
contene; las direcciones de esos objetos en una forma similar a ,la qu~ muest'.a la calcularse mediante
figura,! .2.1 para arreglos lineales. . . , . .. .
Supongamos que un arreglo bidimension.al de enteros esta conservado median- base[al + (2 * s + ,) * 1
te rengln -mayor, como en la figura 1.2.3, y que, para un arreglo ar, base(ar) es la
direccin del primer elemento de,] arreglo. Es qecir, si ar se declara por: . esto .es

base(a) + 1,;
int ar[r1Hr2l;
Puede confirmarse que a[2][4] est 14 unidades despus de base(a) en la figura
donde rl y r2 son los rangos de la primera y segunda dimensin, respectivamente, Y
1.2.3. , , .
base(ar) es la direccin de, ar[O][O].Supongamos tambin que esize es el. tam,ao .de
Otra implantacin posible de un arreglo bidimensional es la siguiente: un
cada elemento del arreglo. Calculemos la direccin de un elemento arbitrario,
arreglo ar, declarado con lmites superiores u 1 y u2, consiste en u 1 + 1 arreglos uni-
ar[il ][2]. Colllo e.l elemento est en. el re,ngln i1 ', su _direccin puede ob_tener~e,al c~l-
dimensionales. El primero es un arreglo ap de apuntado;es u 1. El i-simo elemento de
cular fa direccin del primer elemento dd renglon d Y sulllar la cantidad ,.2 es,ze
ap es un apuntador a un arreglo unidimensional cuyos elementos son los elementos
(esta cantidad representa cun lejos est el elemento. e la c?lu~na i2 d~ntro del
del arreglo unidimensional ar[i]. Por ejemplo, la figura 1.2.4 muestra tal implanta-
rengln il). Pero para alcanzar e.l primer elemento del renglort 11 (es dectr, el ele-
cin para el arreglo a de la figura L2;2, donde ul es 3 y u2 es 5.
mento ar[i]O,), deben. recorrerse 1 renglones completos,. cada uno de los cu~les
. Para referirse a ar[]U], se accesa primero arpara obtener el apur1t~dor.;ar[J.
contiene r2 elementos (dado que hay un elemento de cada columna en cada renglon),
El arreglo en esa localidad del apuntador seaccesa d~spus para obtner a[)]UJ,
De hecho esta, segunda implantacin es la ms simple y directa de l~sdos. Si
.
embargo, los arreglos ul desde ar[OJ hastaar[ul -1) se ubicaran; por lo regular;
o 1.
}
-
2 encabezado
o 1 4
a(Ol [O] - base (a)
a[OJ [\] a(OJ[O! a[OJ[I] a(OJ[2J a[OJ[3] a(0][4J '
n?iigll,0 a[O] [2]
a(Ol (3]
a(OJ (4] rengln O -
a[!] [O]
a[l] (1] rengln l __.. a(l][OJ a(IJ[I] a(\][2] a(IJ[3J a(l](4L
rengln 1 a(!] (2]
a[l] (3] rengln 2 - - . .
a(l] (4]
a(2] [O]
a[2] [11 a(2][0] a[2][1] a[2][2] a(2][3] a(2][4]
rengln 2 a(2] [21
a(2] (j] Figura 1.2.3. Representacin. de
a(2] (4] un arreglo bidimensional: Figura 1.2.4 Implantacin alternativa de un arreglo bidimensional.

Introduccin a la estructura de datos 39


Estructuras de datos en C
38
en forma contigua, con ar[O] seguido de modo inmediato por ar[!], y as sucesiva-
mente. La primera implantacin evita tener que guardar el apuntador del arreglo
extra ap y calcular el valor de un apuntador explcito al rengln deseado del
arreglo. As es ms eficiente en cuanto a tiempo y espacio.
Plano O

Arreglos multidimensionales Rengln o--.. , , /


/
/ / /
/ / /
/ / /
El lenguaje C tambin. permite arreglos de ms de dos dimensiones. Por ,, ,
Rengln l -..,...
, ,
/ /
ejemplo, puede declararse un arreglo tridimensional por medio de: /
/
/
/
/
/

Columna Columna Columna Columna


int b[3][2][,J; o I 2 3

el cual se muestra en la figura l .2.5a. Un elemento de este arreglo se especifica con (a)
tres subndices, por ejemplo b[2][0][3]. El primero indica el nmero de plano, el se-
gundo el de rengln y el tercero el de columna. Tal arreglo es til cuando un valor es 0

..
t determinado por tres entradas. Por ejemplo, un arreglo de temperaturas puede '
indexarse por latitud, longitud y altitud. o 2
Por razones obvias, la analog geomtrica se rompe cuando vamos ms all
Encabezad o< o I
de las tres dimensiones. Sin embargo, el lenguaje C permite un nmero arbitrario ;,- o 3
b[Ol [01 [O] -.---baseb
de dimensiones. Por ejemplo, puede declararse un arreglo de seis dimensiones por b[Ol [O] [!]
medio de: Rengln o
b[OI [01 [21
Plano O >- b[OI [01 [31
int e [7][15][3l[5l[8l[2l; b[O] [11 [01
.
Rengln I b[OI [11 [1]
b[OI [11 [2]
Referirnos a un elemento de este arreglo requenna de seis subndices, como
c[2][3l[Ol[ll[6J[l]. El nmero permisible de diferentes subndices en una posicin
,. .. b[OI [11 [31
b[II [01 [01
particular (el rango de una dimensin particular) es igual al lmite superior de dicha b[II [01 [I]
Rengln O
dimensin. El nmero de. elementos en un arreglo es igual al producto de los rangos de b[I 1 [O] [2]
todas sus dimensiones. Por ejemplo, el arreglo b contiene los elementos 3 * 2 * 4 = 24 Plano l >- b[IJ [O] [3]
y el e 7 * 15 * 3 * 5 * 8 * 2 = 25200 elementos. b[II [IJ [O]
La representacin mediante rengln-mayor de arreglos puede extenderse a Rengln l b[II [!] [I]
arreglos de ms de dos dimensiones. La figura l.2,?b ilustra la representacin del b[IJ [I] [21
arreglo b de la figura l .2.5a. Los elementos del arreglo e de seis dimensiones descrito ~
b[IJ [I] [3]
antes se ordenan de la siguiente manera: b[2] [O] [01
Rengln O . b[21 [01 [11
b[2J [O] [2]
C[O][O][Ol[O][O][Ol Pla'n() 2 b[2] [01 [31
>-
C[OJ[O][Ol[OJ[OJ[1l b[2] [l] [O]
[DllDllDllDll1llDJ b[2] [I] [11
C[O][Ol[Dl[OJ[1][1l
Rengln I
b[21 [11 [21
C[O][Ol[O][OJ[2][0] b[2] [!] [3]
'
(b)
C[6][1s][2][,J[5][0]
C[6J [ 1 ' ] [2] [, l [5] [1]
Figura 1.2.5 Arreglo tridimensional
e e 6 J 1, J 21 , J e6.J o J

40 Estructuras de datos en C Introduccin a la estructura de datos


41
~

!T
EJERCICIOS
c(6JIL,J[2J[sJ16ll1l
C[6ll1sll2Jlsll7J[DJ 1.2. l.a. La mediana de un arreglo de nmeros es el elemento m para el cual la mitad del
Cl6Jl1,J1211,1111111 resto de los elementos del arreglo es mayor o igual que m y la otra mitad es menor
o igual que m, si el nmero de elementos es impar. Si es par, la mediana es el pro-
Es decir, el ltimo subndice vara con ms rapidez; los subndices no se incrementan medio del par de elementos m l y m2 para el cual una mitad de los elementos res-
hasta que no se hayan agotado todas las posibles combinaciones de subndices a su tantes del arreglo es mayor o igual que m 1 y m2 y la otra mitad es menor o igual
derecha. Esto se parece a un odmetro (el marcador de kilmetros recorridos es simi- que m l y m2. Escriba una funcin en lenguaje C que acepte un arreglo de nme-
lar al cuentamillas de un coche) donde el dgito de la extrema derecha cambia mucho ros y regrese su mediana.

ms rpido.
b. La MODA de un arreglo de nmeros es el nmero m del arreglo que se repite con fl
mayor frecuencia. Si hay ms de un nmero que se repite con igual frecuencia m-
Qu mecanismo se necesita para accesar un elemento de un arreglo multidi- xima. no existe la moda. Escriba una fncin en lenguaje C que acepte un arreglo } \
mensional arbitrario? Supngase que ar es un arreglo de n-dimensiones declarado 1
de nmeros Y:,regrese su moda q una indicacin de. que no existe. '
por 1.2.2. Escriba un programa en lenguaje C que haga lo siguiente: lea un grupo de registros
de temperatura. Un registro consta de dosnrneros; un entero entre -90 y 90 que
int ar[r1J[r2J ... [rnJ; representa la latitud en la que se tom el registro y la temperatura observada en esa
,-el cual se almacena en orden mediante rengln-mayor. Suponemos que cada elemen- latitud. Imprima .una tab~~ que consista de cada latitud y de la temperatura prome-
dio er,i esa latitud., Si no exis,ten registros para una latitud particular, imprimir "NO
to de AR ocupa esize localidad.es de memoria y definimos base(ar) como la direccin
HAY .DATOS" en lugar del promedio. Imprima luego las temperaturas promedio
del primer elemento del arreglo (esto es, ar[O][O] .... [O]). Entonces, para accesar el en los hemisferios norte y spr (el norte consiste en las latitudes de la 1 a la 90 y el sur
elemento en las latitudes de la ,---,1 a la -,-90). (Esta temperatura promedio .debiera computar-
se como promedio de promedios y no como promedio de los registros originales).
ar[i1l[i2J ... [inl; Determine tambin cul hemisferio es ms clido. Para hacerlo, ton:ie el promedio
de temperatura en .todas las :latitudes de cada hemisferio para las cuales hay datos,
es necesario primero pasar a travs de los il "hiperpla'nos" completos, cada uno de tanto en esa latitud coino en la correspondiente en el-otro hemisferio. (Por ejemplo,
los cuales consiste en r2 * r3 ... * . rn elementos para alcanzar el primer elemento si hay datos para la la:fitud 57 pero no para la -57, entonces debe ignorarse la tem-
de ar, cuyo primer subndice es il. Despus es necesario pasar por los i2 grupos de , Peratura promedi para1a latitud 57 en la_ determinacin de Clll hemisferio es ms
r3 * r4 ... rn elementos para alcanzar el primer elemento de ar, cuyo primer Y clido).
segundo subndices son il e i2, respectivamente. Debemos llevar a cabo un proceso 1.2.3. Escriba un programa para una cadena de 20 tiendas de departamento, cda una de
similar a travs de las otras dimensiones, hasta que alcancemos el primer elemento, las cuales vende 10 artculos diferentes. Todos los meses, cada supervisor marida
cuyos primeros n-1 subndices igualen a los. del elemento deseado. Por ltimo, es una ficha de datos para cada artculo, la cual comprende el nmero de tienda (de 1 a
necesario pasar por in elementos adicionales para alcanzar el elemento deseado. 20), el nmero de artculo (de 1 a 10) y una cifra de venta (menor de $OO,OOO) que
representa el monto de las ventas de ese artculo en esa tienda. Sin embargo, algu-
As que la direccin de ar[il][i2] ., .. [in] puede escribirse como base(ar) + esize nos supervisores pueden no mandar ficha para algunos artculos (por ejemplo, no
* [il * r2 * ... * rn + i2 * r3 * ... rn + .,. + (i(n - 1) * rn + in)], lo que puede todos los artculos son vendidos en todas las tiendasJ. Se tendr que escribir un
programa para leer esas fichas de datos e imprimir una tabla con.12 columnas. La
evaluarse de manera ms eficiente al usar la frmula:
primera columna debe contener los nmeros de tienda del 1 al 20 y la palabra "TO-
TAL" en la ltima lnea. Las siguientes 10 columnas deben contener las cifras de
base(ar) + esize * venta de cada uno de los diez artculos para cada una de las tiendas con el total
[ in + rn ( i( n - 1) + r( n - 1) ( ... + r3 * ( i2 +
r2 * i1) ... ) ) l de ventas de cada artculo en la ltima lnea. La ltima columna debe contener el to-
tal de ventas de cada una de las 20 tiendas para todos los artculos, con la cifra del
total de ventas global de la cadena en la. esquina inferior. derecha. Cada columna de-
Esta frmula puede evaluarse con el siguiente algoritmo, que calcula la direc-
ber l_levar. un encabezado apropiado .. Si no se registran ventas. de un artculo y tien-
cin del elemento del arreglo y la coloca en addr (suponiendo arreglos re i de tama- da particular,. se supondrn cero ventas. No suponga que su ,entrada est en algn
o n para guardar los rangos y los ndices, respectivamente): orden particular.
1.2.4. Muestre cmo puede representarse en lenguaje C un tablero por medio de un
offset= O; arreglo. Muestre cmo representar el estado de un juego de damas en un instante
for (j = o; j < n; j++) particular. Escriba una funcin en lenguaje C que sea una entrada a un arreglo que
offset= r[jl offset+ i[jl; represente tal tablero y que imprima todos los posibles movimientos de las negras
addr = base(ar) + esze * offset;
que puedan hacerse a partir de esa posicin.

43
tijili
1 '. 42
Estructuras de datos en C Introduccin a la estructura de datos

[ 't '
1.2.5. Escriba una funcin prinar\,i) que acepte un arreglo a de m por n enteros e impri~ Cmo pueden almacenarse en forma secuencial estos elementos en la memoria?
ma los valores del arreglo en varias pginas de la siguiente manera: cada pgina Desarrolle un algoritmo para accesar a[i]Ul, donde i > = j. Defina u11 arreglo
debe contener 50 renglones y 20 columnas del arreglo. A lo largo del tope de cada triangular superior de manera anloga y haga lo mismo que para el triangular
pgina deben escribirse encabezados "COL O", "COL l", y as sucesivamente; as inferior.
~orno A"RENGO, RENO l", etc. a lo largo del margen izquierdo. El arreglo debe b. Un arreglo estrictamente triangular inferior a es un arreglo den por nen el cual
1~pnm1rse, P?r subarreglos. Por ejemplo, si a fuera un arreglo de 100 por 100, la a[i]I/J = = O si i < = j. Responda las preguntas de la parte a para un arreglo de
pnmera pagma contendra del a[OJ[OJ al a[49][19], la segunda del a[OJ[20] al este tipo.
a[49][39], la tercera del a[OJ[40] al a[49][59], y as hasta la quinta pgina que c. Sean a y b dos arreglos triangular inferior den por n. Muestre cmo puede usarse
conte.ndria del a[OJ[SOJ al a[49][99], la sexta contendra del a[50J[O] al a[99][19] y as un arreglo e de n-por-(n + 1) para contener todos los elementos distintos de cero
sucesivamente. La impresin completa ocupara 10 pginas. Si el nmero de rengle~ de los dos arreglos. Cuiies elementos de e representan los elementos a[i]Ul y
nes no es un mltiplo de 50 o el nmero de columnas no lo es de 20 las ltimas b [i]U], respectivamente?
pginas de la impresin debern contener menos de 100 nmeros. d. Un arreglo trldiagonal a es un arreglo de n por n en el cual a[i] U1 = ~ O, si el va-
1.2.6. Suponga que cada elemento de un arreglo a que est almacenado en orden mediante lor absoluto de i - j es mayor que 1. Cul es el nmero mximo de elementos
rengln mayor ocupa cuatro unidades de memoria. Si se declara a de cada una de distintos de cero en tal arreglo? Cmo pueden almacenarse estos elementos en
las siguientes maneras Y la direccin del primer elemento de a es 100 encuentre la forma secuencial en la memoria? Desarrolle un algoritmo para accesar a[i]l/1 si el
direccin del elemento del arreglo que se indica: ., - ' valor absoluto de i - j es l o menor. Hgase lo mismo para un arreglo a en el
a. int a[1DDJ; direccin de a[1Dl
cual a[i]l/1== O, si el valor absoluto de (i - j) es mayor que k.
b. int a[2DDJ; direccin de a[1DDJ
c. int a[1DH2Dl; direccinde a[OJ[DJ
d. 1nt a [ 1 O][ 2 Ol ; direccin de a [ 2 ][ 1 J 1.3- ESTRUCTURAS EN LENGUAJE C
e. int a[10][2Dl; direccin de a[5][1J
f. in t a [ 1 O ][ 2 O l ; direccin de. a [ 1 l[ 1 o J En esta seccin examinamos la estructura de datos del lenguaje C llamada estructu-
g. int a[10H2DJ; direccin de a(2][1DJ ra. Suponemos que el lector est familiarizado con la estructura por medio de
h. int a[10][2Dl; direccin de a[5](3J un curso introductorio. En esta seccin repasamos ss elementos sobresalientes Y
i. int a(1DH2Dl; direccin de a[9][19] puntualizamos algunas caractersticas importantes necesarias para un estudio ms
. 1.2. 7. Escriba un~ .funcin lstoff en lenguaje C que acepte como parmetros a dos
general.
arreglos un1d1men:Sionales del mismo tamao: range y sub.range i"epreSentan el ran- Una estructura es un grupo de objetos en el que se identifica a cada uno me-
go de_ un arreglo de enteros. Por ejemplo, si los elementos de range so~ diante su propio identificador, cada uno de los cuales se conoce como miembro de la
estructura. (En muchos otros lenguajes de programacin se llama "registro" (re'
3 5 10 6 3
cord) a una estructura y "campo" (field) a un miembro. Se puede usar de vez en vez
range representa un arreglo a decla'.ado por estos .trminos en lugar de "estructura" y "miembro", .aunque ambos trminos
tienen un significado diferente en el lenguaje C. Por ejemplo, considrese la siguien-
int a[3 [5H1Dl [ful [3]; te declaracin:

Los :iementos de sub representan subndices del arreglo inmediato anterior. Si struct
sub[l] no se encuentra entre OYrange[i] - 1, faltan todos los subndices del r-simo char first[1Dl;
en adelante. En el ejemplo anterior, si los elementos de sub son char midinit;
char last[20J;
l 3 l 23 snarne, enarne

sub representa el arreglo unidimensional a[l][3][1][2]. La funcin /istoffdebe impri- Esta declaracin crea dos estructuras variables, sname y ename, cada una de
mir los desplazamientos desde la base del arreglo a representad() po'r range de todos las cuales contiene tres miembros:firs/, midinil y las/. Dos de esos miembros son ca-
los elementos de a que estn incluidos en el arreglo (o el desplaZ.miento del nico denas de caracteres y el tercero es un carcter simple. Tambin se puede asignar una
elemento si todos los subndices estn dentro de los lmites) representado por sub. etiqueta a la estructura y declarar luego las variables por medio de ella. Por ejemplo,
Suponga que el tamao ksize de cada elemento de a es 1. En el ejemplo anterior Jis- considrese la siguiente declaracin que hace lo mismo que la anterior:
toff deber imprimir !os valores 4, 5 y 6. '
1.2.8.a. ~n a:reglo :rianguta: inferior a es un arreglo den por nen el cual a[i] UJ = = o, si struct nametype {
t < J. Cual es el numero mximo de elementos distintos de cero en tal arreglo? char first(l.O]

44 Estructuras de datos en C Introduccin a la estructura de datos 45


~'~
char midinit; Puede declararse un miembro de una estructura como otra estructura. Por
1 char lastl2Dl; ejemplo, dada la definicin precedente de nametype y la siguiente definicin de
) ; addrtype
struct nametype sname, ename;
struct addrtype {
Esta definicin crea una etiqueta de estructura nametype que contiene tres char straddr[Ol;
miembros, first, midinit y /ast. Una vez definida la etiqueta de la estructura, pueden char city[1Dl;
declararse las variables sname y ename. Para mayor claridad del programa, es reco- char state[2J;
mendable declarar una etiqueta para cada estructura y luego declarar variables char zip[SJ;
mediante dicha etiqueta. };
Otra alternativa para la estructura de etiqueta es usar la definkin typedef en
C. For ejemplo puede declararse una nueva etiqueta de estructura nmadtype con

struct nmadtype {
typedef struc.t
struct nametype name;
char first[1Dl;
struct addftype address;
char': mia ini t ;
};
char last[2DJ;
NAMETYPE;
Si se declaran dos variables
dice que el identificador NAMETYPE es sinnimo de la estructura precedente don-
dequiera que ocurra NAMETYPE. Podemo.s entonces declarar: struct nmadtype nmad1, nmad2;

NAMETYPi sname, en~me; las siguientes son instrucciones vlidas:

para lograr las declaraciones de las variables de estructura sname y ename. Ntese. nmadL.name.midinit = nmad2.name.midinit;
que las etiquetas de las estructuras se escriben convencionalmente en minsculas pero nmad2.address.city[4J = nmadt.name.first[LJ~
la especificacin typedef se escribe con maysculas en los programas en C. typedef fer (i-1; i < 10; i++)
nmadL.name.first[iJ = nmad2.name.first[iJ;
se usa a veces para dar la apariencia de una especificacin de, un. ADT dentro de un
programa en C.
Una vez declarada una,variable como estructura, cada uno de sus. miembros Algunos de los compiladores ms nuevos de C, as como el estnd~r ANSI de-
puede accesarse al especificar el nombre de la variable y el identificador del miembro sarrollado, permiten asignaciones de estructuras del mismo tipo. Por ejemplo, la
del objet.o separados por un punto. As que la instruccin instruccin nmadl ; nmad2, podra ser vlida y equivalente a

nmad1.name = nmad2.name;
printf( 11 %s 11 , sname.first);
nmad2.address = nmad2.address;

puede usarse para imprimir el primer nombre en la estructura sname y fa instruccin Lo cual, a su vez, equivale a

ename.midinit = 'm' for (i=D; i < 10; i++)


nmadL.name~first[iJ = nmad2.name.first(iJ;
puede sarse para poner la inicial intermedia de la estructura ename a la letram. Si nmad1.~;~e.midiriit = nmid2.name.midinit~
un miembro de una estrutura es un arreglo, tiene que usarse un subndi'e para acce- fer ( 1-0; i < 20; i++)
nmadL.name.last[iJ = nmad2.name.last[iJ;
sar un elemento particular del arreglo, como en:
fer (i-D; i < ,o; i++)
nmad 1 :aqdtess. straddr [ i J = nmad2. address. s traddr [ i. J ;
far (i=D; i < 20; i++) far ('i=Di t < 10; i++)
sname.last[iJ = ename.last[iJ; nmadL.address.city[il = nmad2.address.city[i];

46 Estructuras de datos en C Introduccin a la estructura de datos 47


Esta instruccin asegura, primero, que el registro del estudiante y del empleado se
for (i=O; i < 2; i++)
nmad1.address.state(i] = nmad2.address.state[i]; refieran a la misma persona al comparar sus nombres. Ntese que no puede simple-
for ( 1-0; i < 5; i++) mente escribirse
nmad1.address.zip(i] = nmad2.address.zip[i];
if (e.nmaddr.name s.nmad.name)
Sin embargo, como el lenguaje C original, tal y como fue definido por Kernighan y
Ritchie y otras versiones implantadas de C no permiten la asignacin de estructuras,
dado que no pueden compararse dos estructuras con una sola operacin en C.
no se utilizar esta caracterstica en el resto del libro.
Puede haberse notado que se usan dos identificadores diferentes, nameaddr y
Considrese otro ejemplo del uso de estructuras, en el que definimos estructu-
nmad para los miembros nombre/direccin en los registros del estudiante y el
ras que describen un empleado y un estudiante, respectivamente:
empleado, respectivamente. No es necesario hacerlo, puede usarse el mismo identifi-
cador para nombrar los miembros de tipos de estructura diferentes. Esto no causa
struct date { ninguna ambigedad ya que el nombre de un miembro debe siempre ir precedido por
int month; una expresin que identifica la estructura de un tipo especfico.
int day;
int year; Implantacin de ;estruciuras
) ;
struct position
char deptno[2J; P~semos ahora de la aplicacin de estrncturas a su implantacin. <::ualquier ti-
char jobtitle[20l; po de datos en C puede concebirse como un patrn o plantilla, Cori'esto se quiere de-
) ; cir que un tipo es un mtodo .de interpretar una porcin de la memoria. Cuando se
struct employee { declara de un cierto tipo a tia variable, .se quiere.decir que el identificador se refiere
struct nmadtype nameaddr; a una cierta porcin de memoria y que los contenidos de esa memoria deben in-
struct position job; terpretarse de acuerdo al patrn que define el tipo, el cual especifica ambas cosas: la
float salary; cantidad de memoria apartada para la variable y el mtodo mediante el cual se in-
int numdep; terpreta dicha memoria.
short int hplan; Por ejemplo, supngase que bajo cierta implantacin de C se representa un en-
struct date datehired;
tero con cuatro bytes, un nmero de punto flotante con ocho y un arreglo de 10
) ;
struct student
caracteres con diez .. Entonces las declaraciones:
struct nmadtype nmad;
int -x;
float gpindx;
float y;
int cr-edits;
struct date dateadm; char z[1DJ;
);
especifican que deben apartarse cuatro bytes de memoria para x, ocho para y y diez
para z. Una vez que se apartaron esos bytes para esas variables, los nombres x, y y z
Al suponer las declaraciones
siempre se referirn a las localidades correspondientes. Cuando se haga referencia a
x se interpretarn sus cuatro bytes como un entero, cuando se haga ay, se interpreta-
struct employee e;
struct student s; rn sus ocho bytes como un nmero real y cuando se haga a z, se interpretarn sus
diez bytes como una coleccin de diez caracteres. La cantidad de memoria apartada
la instruccin para aumentar !00/o a un empleado con un ndice de calificaciones co- para cada tipo as como el mtodo segn el cual deben interpretarse los contenidos
de dicha memoria, vara de una mquina e implantacin en Ca otra. Pero dada una
mo estudiante superior a 3.0 es la siguiente:
implantacin en C, cualquier tipo indica siempre una cantidad de merrioria y un
mtodo especifico para interpretarla.
if ((e.nameaddr.name.first == s.nmad.name.first) &&
(e.nameaddr.name.midinit == s.nmad.name.midinit) && Supngase ahora que se define una estructura por
(e.nameaddr.name.last == s.nmad.name.last))
if (s.gpindx > 3.0) struct structtype
int field1;
e.salary *= 1.10;

Estructuras de datos en C
lntroduccl6n a la estructura de datos 49
48
(4), o 204. Los contenidos de los 8 bytes 204 a 211 se establecen.para el nmero de
float field2 punto flotante calculado al evaluar la expresin.
char field3 [ 10 l;
Ntese que el proceso para calcular la direccin del componente de una estruc,
};
tura es muy similar al que se lisa para calcular la direccin del componente de un
y se declara una variable arreglo. En ambos casos, se suma un desplazamiento que depende del seleccionador
del componente (el identificador del miembro o el valor del subndice) a la direccin
struct structtype r; base de la estructura compuesta (la estructura o el arreglo). En. el caso de una estruc-
tura, se asocia el desplazamiento con el identificador del campo mediante la defini-
Entonces, la cantidad de memoria especificada por la estructura es la suma de las cin de tipo, mientras que en el caso del arreglo, se calcula el desplazamiento de
cantidades de memoria especificadas por cada uno de los tipos de sus miembros. De acuerdo con el valor del subndice.
tal manera que l espacio requerido para la variable res la suma de_! espacio requeri- Pueden combinarse estos dos tipos de direccionamiento (estructura y arreglo).
do para un entern(4 bytes), un nmero de punto flotante (8 bytes) y un arreglo de 10 Por ejemplo, para calcular la direccin de r.jield3[4J, primero se emplea el direc-
caracteres (10 bytes). En consecuencia, deben apartarse 22 bytes para rOLos 4 prime- cionamiento de la estructura para determinar la direccin base del arreglo r.fie/d3 y
ros se interpretan corno un entero, los 8 siguientes como un nmernde punto flota- luego se usa el direccionamiento del arreglo para calcular la localizacin del quinto
te y los diez ltimos como un arreglo de caracteres. (Esto no siempre es cierto. En elemento de dicho arreglo. La direccin base de r.jield3 est dada por la direccin
algunas computadoras, los objetos de cierto tipo no pueden comenzar en cualquier base de r (200) ms el desplazamiento de jie/d3(l2), que es 212. La direccin de
parte de la memoria, sino que deben comenzar a partir de ciertas "fronteras". Por r.jield3[4J se determina despus como la direccin base de r.jield3 (212) ms 4
ejempl~, un entero de cuatro bytes podra tener que cqmenzar a partir de una direc- (el subndice 4 menos el lmite inferior del arreglo, O) multiplicado por el tamao de
cin divisible por cuatro y un nmero real de longitud. 8 bytes. a partir de una divi- cada elemento del arreglo (1), lo que es igual a 212 -1- 4 * l, o 216.
sible por 8. De talmanera que en nuestro ejempl, si .la direccin de inicio de r fuese Como ejemplo adicional considrese otra variable, rr, declarada por
200, el entero ocupar los bytes del 200 al 203, pero el nmero ;ea! no podra
comenzar en el byte 204 dado que esa localidad no es divisible por ocho. As que el struct structtype rr(20J;
nmero reaLtendra que comenzar en lalocalidad 208 y el regis/rq completo
requerira de 26 bytes en lugar de 22. Los bytes desde .el 2_04 hast el 207 son espacio rr es un ejemplo de arreglo de estructuras. Si la direccin base de rr es 400, entonces
desperdiciado). . . . ... . .. la direccin de rr[I4J. field3[6J puede calcularse como sigue. El tamao de cada
Para cualquier refer~ncia a un miembro de una estrnctura debe ckulatse una componente de rr es 22, as que la localidad de rr[l4) es 400 + 14 * 22 o 708. La
direccin. Con cada idendficador ce los rniembros de una estructura se asocia un no.
direccin base de rr[l4] . .field3, ser entonces 708 + 12 o Pqrconsiguiente, la di-
desplazamiento que especifica qu tan lejos del inicio de la estruciuraest la locali' reccin de rr[l4]. fie/d3[6] es 720 + 6 * 1 o 726. (Esto ignora, de nuevo, la posibili-
dad de dicho miembro. En el ejemplo precedente, el desplazamiento dejield1 es O, el dad de restricciones de frontera. Por ejemplo, aunque el tipo rectype puede requerir
dejield2 es 4 (suponiendo que no hay restricciones de frontera) y el defield3 es 12. slo 22 bytes, cada rectype tal vez tenga que empezar en una direccin divisible por
Asociada a cada variable de estructura hay una direccin base, que es la_ localidad cuatro, de tal manera que se desperdicien dos bytes entre cada elemento de rr y su
del principio de la memoria asignada a dicha variable. Estas asociacines las estable- vecino. Si esto sucede, entonces el tamao real de cada elemento es 24, por lo que la
ce el compilador y no deben importar al usuario. Para calcularse la localidad de un direccin de rr[l4]. field3[6J en realidad es 754 en lugar de 726).
miembro de una estructura, se suma al desplazamiento del identificador del miem-
bro la direccin base de la variable de estructura. Uniones
Por ejemplo, supngase que la direccin base de res 200. Entonces lo que en
realidad ocurre al ejecutar la instruccin Todas las estructuras vistas hasta ahora tenan miembros. fijos y formato ni-
co. El lenguaje C tambin permite otro tipo de estructura, la unin, mediante la cual
rfield2 - r. field.1 + 3. 7; puede interpretarse una variable de distintas maneras.
Por ejemplo, considrese una compaa de seguros que ofrece tres tipos de
es lo siguiente: primero, se determina la localidad de r.fieldl como la direccin base pliza: de vida, de automvil y de vivienda. Un nmero identifica cada pliza
de r (200) ms el desplazamiento de campo de fie!dl (0), lo que es igual a 200.Los de seguro, de cualquier tipo que sea. Para los tres tipos es necesario tener nombre,
cuatro bytes en las localidades 200 a 203 se interpretan como un entero. Despus se direccin, cantidad asegurada y prima mensual del asegurado. Para las. plizas
convierte este entero a nmero de punto flotante y luego sesuma al 3,7. El resultado de automvil y vivienda es necesaria la cantidad deducible. Para el seguro de vida se
es un nmero de punto flotante que abarca 8 bytes. Despus se calcula la localidad necesita la fecha de nacimiento del asegurado y del beneficiario. Para la de autom-.
de r.jield2 como la direccin base de r (200) ms el desplazamiento del campo field2 vil, se requiere el nmero de licencia, el estado, el modelo y el ao. Para la de vivien-

Introduccin a la estructura de datos 51


50 Estructuras de datos en C
da, se necesita la constancia de antigedad y el tipo de dispositivos de seguridad. El Ahora que se ha examinado la sintaxis de la definicin de unin, examinemos
tipo de estructura de pliza para una compaa como la anterior puede definirse su semntica. Una variable declarada como de tipo unin T (por ejemplo, struct
como una unin. Se definen primero dos estructuras auxiliares. po/icy p) contiene siempre todos los miembros fijos de T. As, siempre es vlido re-
ferirse ap.name, p.premium o p.kind. Sin embargo los miembros de unin incluidos
en el valor de esa variable depende de lo que haya almacenado el programador.
#define LIFE 1 Es responsabilidad del programador asegurar que es compatible el uso de un
#define AUTO 2
miembro con lo que se ha puesto en esa localidad. Una buena idea es tener un
#define HOME 3
miembro fijo separado en una estructura que contenga una unin cuyo valor indique
struct addr { cul es la alternativa que se usa en ese momento. En el ejemplo precedente, se usa el
char street[SDJ; miembro kind con ese propsito. Si su valor es LJFE (!), entonces la estructura con-
char city[1Dl; tiene una pliza de seguro de vida, si es AUTO (2) una de automvil y si es HOME
char state[2J; (3) una de vivienda. As, el programador debe ejecutar un cdigo similar al que sigue
char zip[SJ; para referirse a la unin:
}; .
struct date. {
int month; if (p.kind == LIFE)
int day; printf(''ln%s %2d//%2d//%~d'',
int year; p.policyinfo.life.beneticiary,
}; p.policyinfo.life.birthday.month,
struct policy p.policyinfo.life.birthday.day,
int pOlnumber; p.policyinfo.life.birthday.year);
char name[30); else if (p.kind == AUTO)
~truct addr address; printf( 11 /n%d %s %s %s %d 11 , p.policyinfo.auto.autodeduct,
int amount; p.policyinfo.auto~license,
float prmium; p.policyinfo.auto.state,
int kind; I* LIFE, AUTO o HOME *I p.policyinfo.auto.model,
union { p.policyinfo.auto.year);
struct else if (p.kind == HOME)
char beneficiary[3DJ; printf( 11 /n%d %d 11 , p.policyinfo.home.homededuct,
struct date birthday; p.policyinfo.home.yearbuilt);
} life; else
struct { printf("/tipo incorrecto %den kind", p.kind)/
int. autodeduct;
char license[~Ql; En el ejemplo anterior, si el valor de p.kind es LIFE, p contiene los miembros
char state[2); beneficiary Y binhday. No es vlido hacer referencia a mode/ o yearbuilt cuando el
char modell15l;
valor de kind sea LIFE. De manera similar, si el valor de kind es AUTO, puede refe-
int year;
rirse a autodeduct, license, state, model, y year pero a ningn otro miembro. Sin
auto;
struct {
embargo, el lenguaje C no requiere un miembro fijo para indicar la alternativa ac-
in"t homededuct; tual de una unin ni nos impone el uso de una alternativa particular dependiendo del
int yearbtiilt; valor fijo de un miembro.
home; . . Una unin permite que una variable tome varios "tipos" diferentes eri puntos
distrnt~s de una ejecucin. Tambin permite que un arreglo contenga objetos de
policyinfo;. tipos diferentes. Por ejemplo, el arreglo a, declarado por

Examinemos la unin con ms detalle. La definicin consiste en dos partes: struct policy a[1DDJ;
una fija y una variable. La parte fija consta de todas las declaraciones de los
miembros hasta la palabra reservada unin, mientras que la part variable consiste puede contener plizas de automvil, de vivienda y de vida. Supngase que se decla-
en el resto de la definicin. ra un arreglo a Y que se desea aumentar en 5% las primas de todas las plizas de

52 Estructuras de datos en C Introduccin a la estructura de datos 53


seguro de vida y vivienda contratadas antes de 1950. Puede hacerse de la siguiente
manera: Los diferentes miembros de la unin se superponen unos a otros. En el ejemplo
anterior, si se le asigna espacio a samp/e desde la localidad 100, de tal manera que
for (i=O; i<100; i++) samp/e ocupe los bytes del 100 al 131, los miembros fijos sample.jl, samp/e.}2 y
if (a[il.kind == LIFE) sample.utype ocupan los bytes del 100 al 103, del 104 al 111 y del 112 al 115, respec-
a[iJ.premium = 1.05 * a[i).premium; tivamente. Si el valor del miembro utype es INTEGER (es decir 1), samp/e.junion.-
else if (a[il.kind == HOME && x.f3 y sample.junion.x.j4, ocupan los bytes del 116 al 119 y del 120 al 123, respecti-
a[il.policyinfo.yearbuilt < 1950)
vamente y no se usan los bytes del 124 al 131. Si el valor de sample. utype es REAL
a[iJ .premium 1.os * a[iJ.premium; (es decir 2), sample.junion.y.j5 ocupan los bytes del 116 al 123 y samp!e.junion.y.j6
del 124 al 131. Esto sucede porque slo puede existir un miembro nico de la unin
Implantacin de uniones.
en un instante. Todos los miembros de la unin usan el mismo espacio y este espacio
puede utilizarlo a la vez uno y slo uno de ellos. El programador determina qu
miembro es el apropiado.
Para entender por completo el concepto de unin es necesario examinar su
implantacin. Una estructura puede considerarse como el mapa de un camino hacia
un rea de la memoria: define cmo debe interpretarse la memoria. Una unin pro- Parmetros de una estructura
vee varios mapas de camino diferentes para la misma rea de la memoria y es res-
ponsabilidad del programador de.terminar cul de ellos se usa en cada momento. En En el lenguaje C tradicional, una estructura no puede pasarse a una funcin median-
la prctica, el compilador asigna suficiente memoria para contener el miembro ms te una llamada por valor. Para pasar una estructura a una funcin, tenemos quepa-
grande de la unin. Es.el mapa, sin embargo, el que determina cmo debe interpre- sar su direccin a la funcin y referirnos a la esuctura por med.o de un apuntador
tarse la memoria. Por ejemplo, considrese la unin simple y las estructuras (es decir, se llama por referencia). La notacin p - x en C, es equivalente a la nota-
cin (*p).x y se usa con frecuencia para referirse a un miembro de un parmetro de
#define INTEGER 1 estructura. Por ejemplo, la siguiente funcin imprime un nombre en un formato
#define REAL 2 tabular y devuelve el nmero de caracteres impresos:
struct stint
int f3, f-'; writename (name)
}; struct nametJpe *name;
struct stfloat {
float fS, f6; int count, i;
};
struct sample printf( 11 \nu);
int f1; count = O;
float f2; for (i=O; (i < 10) && (name->first[il != '\n); i++) {
int utype; printf( 11 %c' 11 , narne->first[i])
union { count++;
struct stint x; !* fin defor *I
struct stfloat y; printf( uzcu, , ,);
funio'n; count++;
}; if (name~>midinit != 1 1 )
printf( 11 %c%s 11 , name->midinit, 11 .11);
Supngase de nuevo una implantacin en la cual un entero requiere 4 bytes y count += 3;
un nmero de punto flotante 8. Entonces, los tres miembros fijos j1,.j2 y utype !* fin de if *I
ocupan 16 bytes. El primer miembro de la unin, x, requiere 8 bytes, mientras que el for (i=O; (i < 20) && (name->last[il != , \0'); i++) {
printf( 11 %c 11 , name->last[i]);
segundo, y, requiere 16. La memoria que en realidad se asigna a la parte de la unin
count++;
de la variable, es el espacio mximo necesario para cualquier miembro. En este caso, I* fin &flor *I
en consecuencia, son asignados 16 bytes para la parte de la unin de sample. Suma- I:'eturn(count);
dos a los 16 bytes necesarios para la parte fija de la unin, a sample se le asignan 32. I * fin de writename *I

54 Estructuras de datos en C
Introduccin a la estructura de datos 55
'' ,.
~...r., 'i,'
' '. ,

La siguiente tabla ilustra los efectos de la instruccin x = writename (&sname) sobre Como otro ejemplo, supngase que se aade otro miembro, sindex, a la defini-
cin de la estructura emp!oyee. Este miembro contiene un entero e indica el ndice
dos valores diferentes de sname:
acadmico del empleado en el arreglo s particular. Se declara sindex (dentro del
registro) como sigue:
valor de sname.jirsr: "Sara" "Irene"
valor de snmne.midinit: 'M' struct employee
valor de sname.last: "Binder" "LaClaustra'' struct nametype nameaddr;
salida impresa: Sara M. Binder Irene LaClaustra
valor de x: 14 16
struct datehired ... '
int sinGex;
};
De manera similar, la instruccin x = writename (&ename) imprime los valores de
los campos de ename y asigna el nmero de caracteres impresos ax.
El nuevo estndar propuesto para C y algunos compiladores C que permiten la Puedereferirse al nmero de crditos cursados por el empleado i cuando era estu-
asignacin de estructuras permiten tambin pasarlas por valor, sin apHcar el opera- diante rrediante s[e[i] .sindex] .credits.
dor &. Esto implica copiar los valores de la estructura completa cuando se llama la La siguiente fundon puede usarse p~ra dar 100/o de aumento a tods los
funcin. Sin embargo, en este libro no suponemos esta capacidad y pasamos todas empleados cuyo indice atadmico sea. Superior a 3.0 y regresar el nmero de tales
las estructuras por referencia. empleados. Ntese que ya no se tiene que comparar el nombre de un empleado con
Ya se ha. visto que el miembro de una estructura puede ser un arreglo u otra el de un estudiante para asegurarse de que sus registros representan a la misma.per-
estructura. De manera similar puede declararse un arreglo de estructuras. Por sona (aunque esos nombres deben ser iguales si los comparamos). Ms bin, puede
ejemplo, si los tipos emp!oyee y student se declaran como anteriormente, pueden usarse en forma directa el campo sindex para accesar el registr~ ,delestudiante apro-
declarase dos arreglos de estructura emp!oyee y student como: piado para un empleado. Supngase que el programa principal cohtie.ne la declara-
cin
struct employee e[100J;
struct student s[1DJ; struct employee e[100l;
struct student s[100];
El salario del empleado 14 es referido por e[13]. sa!ary y el apellido por e[l3].
nameaddr.name.last. De manera similar, el ao de admisin del primer estudiante es raise2 (e, s)
s[O]. dateadm.year. struct employee e(];
Como ejemplo adicional se presenta una funcin da al principio de un struct student s[l;
nuevo ao para dar 100/o de aumento a todos los trabajadr con ms de l Oaos de (
rreglo de estructuras. int i, j, count;
antigedad y 50/o al resto. Primero, debe definirse un nu
courit = O;
struct employee empset(10DJ; far (i=D; i < too; i++) (
j ~ e[i].sindei;
Y a continuacin: if (s[jl.gpindx > 3.0)
cdurft++;
#define THISYEAR e[il.salary *= 1~10;
raise {e) !*. _end i f -*l
struct ernployee e(J; I * fin de for *
I
{ return(count);
int i; !* fin de raise 2 */

for (i=D; i < LOO; i++) Con mucha frecuencia, se utiliza un arreglo de estructuras grande para conte-
if (e[iJ.datehiced.yeac < THISYEAR - 10) ner una tabla importante de datos, para una aplicacin determinada. En general s-
e[iJ.salary *= 1.10;
lo hay una tabla para cada arreglo de estructuras de este tipo. La tabla de estudiantes
else
e[iJ.salary *= 1.05;
s Y la de empleados e, del ejemplo previo, son buenos ejemplos de este tipo de tablas
/* fin de raise *I de datos. En tales casos, se usan con frecuencia las tablas nicas como variables

Estructuras de datos en C Introduccin a la estructura de datos 57


56
re." -
..
1\.'.

esttico/externas, con una gran nmero de funciones que las accesan, en lugar de int numerator;
como parmetros, lo cual aumenta la eficiencia mediante la eliminacin de la sobre- int denominator;
RATIONAL;
carga que conlleva la transferencia de parmetros. Puede volver a escribirse fcil-
mente la funcin raise2 anterior, para accesar s y e como variables esttico/externas
Con la primera tcnica, un racional r se declara como
en lugar de como parmetros, al cambiar simplemente el encabezado de la funcin a:
struct rational r;
raise2()
El cuerpo de la funcin no necesita cambiarse toda vez, que las tablas s y e se decla- y con la segunda por
ran en el programa externo.
RATIONAL r;
Representacin de otras estructuras de datos
. Podra pensarse que se est listo ahora para definir la aritmtica de nmeros
En 10 que sigue del libro, se usan las estruc't.uras para representar estructuras \ie racwn~les, pero hay un problema importante. Supngase que se definen dos nme-
datos ms complejas. Agregar datos dentrn de una estr\lctura es til porque permite r~s racwnales, rl y r2 y ?u~ se les da un valor. Cmo puede comprobarse si dos
numeras son iguales? Qu1za podra querer programarse
agrupar obj~tos dentro de. una endad simple a.s como nombrar. cada uno de. esos
objetos en forma apropiacla, de acuerdo con su funcin. .
Considrense los problemas de la representacin d," nmeros racionales, como, if (r1-.rium~rator r2. numerator && r1. denominatr =-=
ejemplo de cmo p,ueden usarse las, estructuras de esta manera. r2.denominator)

Nmeros racionales Es decir, ~i tanto los numeradores como los denominadores son iguales los dos n' _
mer~s racionales so~ iguales. Sin embargo es posible que ambos numer~dores y d~-
En la seccin previa se present un ADT para nmeros racionales. Recurdese n_ommadores s~an diferentes y, no obstante, ser iguales los nmerosracionales. Por
que un nmero racional es cualquier nmero que puede expresarse como el cadente ejemplo, los num~ros 1/2 Y 2/4. son iguales, aunque. tanto sus numeradores (l y 2)
de dos enteros. As l /2, 3/4, 2/3 y 2 (o sea 2/1) son nmeros racionales mientras que como sus. denom!nadores (2 y 4) son distintos. Por consiguiente, se necesita una
sqr(2) y ,r no lo son. Una computadora por lo general representa los nmeros nueva forma de pr~bar la igualdad para nuestra representacin.
racionales mediante su aproximacin decimal. Si se dan instrucciones a la computa- . Bren, ~or que son iguales 1/2 y 2/4? La respuesta es que ambos representan
dora para imprimir 1/3, sta responder con .333333. Aunque es una buena aproxi- el m1sm.o racional. U~o entre dos y dos entre cuatro son, ambos, un medio. Para
macin (la diferencia entre 1/3 y .333333 slo es una trimillonsima), no es exacta. Si pr~bar igualdad en numeras racionale.s debe transformrseles a fracciones reduci-
se pregunta. por el valor de 1/3 + 1/3, el resultado ser .666666 (que es igual a ~as. Una v_ez que se ha hecho, puede probarse la igualdad por medio de la simple
.333333 = .333333), mientras que el resultado de imprimir 2/3 podra ser .666667. comparac10n de numeradores y denominadores.
Esto podra significar que el resultado de la pregunta 1/3 + I /3 = = 2/3. podra ser Se defi~e. unaji'<1ccin reducida como un nmero racional para el que no existe
falso! En muchos casos, la aproximacin decimal es muy buena, pero a veces no lo entero que d1v1da exa~tamente rnnto al ~enominador como al-.J')umerador. As, l/2,
es. Por esto, es deseable implantar una representacin de los nmeros racionales 2/3 Y 10/.1, son fracciones reducidas, mientras qe 4/8, 12/18 y 15/6 no 0 son. En
para que pueda ejecutarse la aritmtica exacta. ?uestro ejemplo, 2/4 se reduce a 1/2 de manera que los dos nmeros racionales son
Cmo puede representarse en forma exacta un nmero racional? Puesto que iguales.
un nmero racional consiste en un numerador y un denominador, puede represen- Puede ~sarse un procedimiento conocido como el algoritmo de Euclides para
tarse un nmero racional (rational) mediante estructuras, como sigue: reducir un numero rac1oml a una fraccin reducida cuando el nmero est en la
forma numeradorldeno1111nador. Este procedimiento puede esbozarse como sigue:
struct rational {
int numerator; l. S~, el mayor Y bel menor entre el numerador y el denominador.
int denominator; 2. D1v1dase b entre a, para encontrar un cociente y un resto, q y r respectivamen-
) ; te (es decir, a = q * b + r).
3. Hgase a = b y b = r.
Otra manera de declarar este nuevo tipo es la siguiente: 4. R~ptase los pasos 2 y 3 hasta que b sea igual a o.
S. D1v1da el numemdor y el denominador por el valor de a.
typedef struct {

Estructuras de datos en C Introduccin ala estructura de datos 59


58
11

Como ejemplo, represntese 1032/1976 como fraccin reducida: Al usar la funcin reduce, puede escribirse otra funcin equa/ que determine
cundo son iguales dos nmeros racionales rl y r2. Si lo son, la funcin regresa
denominador= 1976 TRUE, en otro caso, regresa FALSE.
paso O numerador= 1032
paso 1 a= 1976 b = 1032
paso 2 a= 1976 b = 1032 q = r = 944 #define TRUE 1
#define FALSE O
paso 3 a= 1032 b = 944
pasos 4 y 2 a = 1032 b = 944 q = 1 r = 88 equal (rat1, rat2)
paso 3 a = 944 b = 88 struct rational *rat1, *rat2;
pasos 4 y 2 a = 944 b = 88 q = 10 r = 64 {
struct rational r1, r2;
paso 3 a= 88 b = 64
pasos 4 y 2 a = 88 b = 64 q = 1 r = 24 reduce(ratt, &r1);
paso. 3 a = 64 b = 24 reduce(rat2, &r2);
=2 r = 16. if (r1.nu~eratqr r2.numerator &&
pasos 4 y 2 a = 64 b = 24 q
r1.denominator r2.d,enominator)
paso 3 a = 24 b = 16 return(TRUE);
pasos 4 y 2 a = 24 b = 16 q= 1 r = 8 return(FALSE);
paso.3 a = 16 b,= 8 /_ * fin ?e eual * I
pasos 4 y 2 a= 16 b = 8 q =2 r =O
a = 8 b =o Ahora pueden escribirse funciones para ejecutar aritmtica sobre nmeros ra-
paso 3
cionales. Se presenta una funcin para multiplicar dos nmeros racionales y se deja
paso5 1032/8 = 129 1976/8 = 247 como ejercicio el problema de escribir funciones similares para la suma, la resta y la
divisin de dichos nmeros.
As,- 1032/1976 como fraccin reducida. es 129/247.
Escribam~s una funcin para reducir un nmero racional (usanios el mtodo
de etiqueta para declarar racionales). rnultiply (r1, r2, r3) /* r3 apunta aJ resulta.do *I
struct rational *r1, *r2, *r3 I* multiplicando
rlyr2 *I
reduce (inrat, outrat)
~ttucit rationaY *inrat, *outrat; struct rational rat3;

int a, b, rem;
rat3.numerator r1->numerator * r2->numerator;
rat3.denominator r1->denominator * r2->denominator;
if (inrat->numerator > inrat->denominator) reduce(&rat3, r3);
a= inrat->nufuerator; I * fin de multiply *I
b =. inrat->denominator;
!* fin deif *f Asignacin de memoria y alcance de variables
else {
a= inrat->denominator;
b = intat->numerator; Hasta ahora se ha visto la declaracin de variables. Es decir, la descripcin de
I* fin de "else *I un tipo de variable o atributo. Sin embargo restan dos problemas importantes que
while (b ! O) { deben discutirse: hasta qu punto una variable est asociada con un almacenamien-
rem = mod b; to real (esto es, asignacin de memoria)?, y hasta qu punto puede referirse a una
a= b; variable particular un programa (esto es, cul es el aicance de las variables)?
b = rem; En C, se conocen a las variables y parmetros que se declaran dentro de una
J /* fin de while *I funcin como variables automticas, A stas se les asigna memoria cuando se llama
outrat->numerator. != a; la funcin. Cuando .termina la ejecucin de la funcin, se pierde la asignacin. Por
outrat->denominator != a; ello las variables automticas existen slo mientras est activa la funcin. Adems,
/* fin de reduce *I
Estructuras de datos en C Introduccin a la estructura de datos 61
60
,
;'..'.~.';.':;,:i
,:',!
.

..i.l
....

se dice que las variables automticas son locales a la funcin. Es decir, las variables
automticas se conocen slo dentro de la funcin donde estn declaradas y otra fun-
que se define dentro del archivo l hasta el final de archivo l y desde el momento en
que se declara en el archivo 2 hasta el final del archivo 2. Por consiguiente, ambas
funciones, average y mode, pueden referirse a grades.
Ntese que el tamao del arreglo se especifica una sola vez, en el momento en
cin no puede referirse a ellas. .
Las variables automticas (es decir, parmetros en el encabezado de una fun- el cual se define originalmente la variable. Esto ocurre porque una variable que se
cin o variables locales que siguen de inmediato a cualquier llave de apertura) declara explcitamente como externa no puede volver a definirse, ni puede asignrse-
pueden declararse dentro de cualquier bloque y existen hasta que ste se acaba. le memoria adicional. Una declaracin de tipo externa! (externa) slo sirve para
Puede referirse a la variable a travs de todo el bloque a menos que el identificador declarar en el resto del archivo fuente que existe tal variable y que ya se haba creado,
de la misma se declare de nuevo en un bloque interno. Dentro del bloque interno, En ocasiones es deseable definir una variable dentro de una funcin para la
una referencia al identificador es una referencia a la declaracin ms interna y no cual la asignacin se mantenga de memoria a lo largo de la ejecucin del programa.
puede hacerse referencia a las variables de afuera. Por ejemplo, podra ser til mantener un contador local en una funcin que indicara
La segunda clase de variables en C es la de las variables externas. A las va- el nmero de veces que es llamada una funcin. Esto puede hacerse si se incluye la
riables que se declaran fuera de cualquier funcin se les asigna almacenamiento en el palabra static dentro de la declaracin variable. Una variable interna esttica (static)
primer punto donde se les encuentra y existen para el resto de la ejecucin del es local a esa funcin pero sigue existiendo a lo largo de la ejecucin del programa en
programa. El alcance de una variable externa va desde el punto en que se declara vez de que se le asigne memoria y despus se le despeje cada vez que es llamada la
hasta el final del archivo fuente que la contiene. Todas las funciones del archivo funcin. Una variable esttica retiene su valor cuando se termina de ejecutar la fun-
fuente pueden referirse este tipo de variables que estfo ms all de su declaracin cin Y se le vuelve a activar. De manera similar, se le asigna memoria a una variable
y, en consecuencia, se dice que son globales para dichas funciones. externa static slo una vez, pero puede referirse a ella cualquier funcin que siga en
Un caso especial es cuando el programador desea definir una variable global en el archivo fuente.
un archivo fuente y referirse en otro a la variable. Tal variable debe declararse en . Para fines de optimizacin, podra ser til dar instrucciones al compilador pa-
forma explcita como externa. Por ejemplo, supngase que se declara un arreglo de ra mantener el almacenamiento de una variable particular en un registro de alta velo-
enteros que representa una lista de grupos en el archivo fuente !y se desea referirse a cidad en lugar de la memoria ordinaria. A tal variable se.le conoce como variable
l a lo largo del archivo fuente 2. Entonces, podran necsitarse las siguientes decla-. registro (registe,") y se define al incluir la palabra register en la declaracin de una va-
raciones: riable automtica o en los parmetros formales de una funcin. Hay muchas restric-
ciones sobre estas variables, que varan con cada mquina: EL lector deber de
consultar los manuales apropiados para los detallesrespecto a esas restricciones.
archivo 1 #[efine MAXSTUDENTS ... Las variables pueden inicializarse de forma explcita como parte de una decla-
int grades(MAXSTUDENTSJ;
racin. A tales variables se les da conceptualmente sus valores iniciales antes de la
ejecucin. Todas las variables externas y estticas no inicializadas comienzan con o
niientras que las variables automticas y de registro no inicializadas tienen valore~
fin de archivo l
indefinidos,
archivo 2 extern int grades[]~ Para ilustrar esas reglas considrese el siguiente programa (los nmeros.a la iz-
quierda de cada lnea tienen slo propsitos de referencia).
floa t average ()
{ contenido de filel.c.
I * fin de average * I 1 int x, y' z;
float mode() 2 func1 ()
{ 3 {
4 int a, b;
I* fin demode *!
5 X 1;
fin de archivo 2 b y 2.,
7 z 3.,
Cuando se combinan los archivos I y 2 dentro de un programa, se asigna memoria 8 a 1;
para el arreglo grades en el archivo l y la asignacin permanece hasta el final del 9 b 2.,
archivo 2. Dado que grades es una variable externa, es global desde el momento en
1,ntroduccin.a la estructura de datos 63
62 Estructuras de datos en C
%od %d\nH, X, y, z, a, b); La ejecucin del programa nos conduce al siguiente resultado:
10 printf ( 11 %d /o~d ~d
/o

11 / * fin de funcl * I a 1 2 3 1 2
b 1 2 3
12 func2 () e 1 2 3 5
13 { d 1 3 3 1
1.i::; in t a; e 1 4 3 2
f 10 20 30
15 a = s; g 1 4 3
printf("%d %d %d %d\n", x, y, z, a);
16
17 /* fin de func2 *f
Vayamos parte a parte del programa. La ejecucin comienza en la lnea 1, en la
que se definen las variables enteras externas x, y y z. Al ser definidas como externas,
end of-sourc~ file1.c
se conocern de modo global en lo que resta de file l.c (lneas 1 y 7). La ejecucin
contenido de file2.c procede luego hasta la lnea 20, que declara por medio de la palabra extern que las
variables enteras externas x, y y z deben asociarse con las variables del mismo
18 #include <stdio.h> nombre en la lnea l. En este punto no se asigna ningn nuevo almacenamiento, da-
1q #include <flle1.c> do que la memoria slo se asigna cuando esas variables se definen desde el inicio
(lnea l). Al ser externas, x, y y z se conocern en lo que resta de/ile 2.c, c:m la ex-
20 extern int x, y, z; cepcin defunc4 (lneas 38 a 45), donde la declaracin de variables locales automti-
21 main() cas x, y y z (lnea 40) remplaza a la definicin original.
22 { La ejecucin comienza con main(), en la lnea 21, lo cual llama de inmediato a
23 func1();
pr'intf(i'%d %d %d\n 11 , x,. y, z); fimcl.junc1 (lneas 2 a 4), define las variables automticas locales a y b (Hnea 4) y
24 asigna valores a las variables globales (lneas 5 a 7) y a sus variables locales (lneas 8 a
25 func2();
func3(); 9). La lnea 10, en consecuencia, produce la primera lnea de salida (lnea a). Cuando
26
27 func3(); termina de ejecutarse/uncl (lnea 11) se elimina la asignacin de memoria para las
28 func<I(); variables a y b. As que ninguna otra, funcin podr referirse a dichas variables.
29 pri~tf("%d %d %d\n", x, y, z); El control regresa entonces a la funcin principal (lnea 24). La salida se da en
30 f* fin de main *I la lnea b. Luego se llama ajunc2.junc2 (lneas 12 a 17), define una variable auto-
31 func3() mtica local, a, a la cual se le asigna memoria (lnea 14) y un valor asignado (lnea
32 { *I 15). La lnea 16 se refiere a las variables (globales) externasx,y y z antes definidas en
static int b; !* b se inicializa con O
33 la lnea 1 y con valores asignados en las lneas de la 5 a la 7. La salida se da en la lnea
b. Ntese que sera ilegal para func2 intentar imprimir un valor para b, dado que
3,; y++; esta variable dej de existir, al ser asignada slo dentro defuncl.
35 b++;
printf(t'%d %d %d %d\n 11 , i, y, z, b); Despus, el programa principal llama afunc3 dos veces (lneas 26 a 21).junc3
36 (lneas 31 a 37), le asigna memoria a la variable local esttica b y la inicializa con O
37 I * fin de func3 *I
(lnea 33), cuando se llama por primera vez. b ser conocida slo parafunc3, sin em-
38 func4 () bargo existir todo el resto de la ejecucin del programa. La lnea 34 incrementa la
39 1 variable global y la lnea 35 hace lo mismo con la variable local b. La lnea d de lasa-
t;O int x, y, z; lida se imprime despus. La segunda vez, el programa principal, llama ajunc3, sin
asignar memoria a b; as que, cuando se incrementa b en la lnea 35 se usa su valor
X= 10; antiguo (de la llamada previa afunc3). El valor final de b reflejar, as, el nmero de
y 20; veces que se ha llamado func3.
z 30; La ejecucin contina despus en la funcin principal main que invoca afunc4
printf( 11 %d %d %d\n 11 , x, y, z)
(lnea 28). Como se haba mencionado antes, la definicin de las variables enteras
I * fin de func4 *I automticas e internas x, y y zen la lnea 40 remplaza su definicin en las lneas l y
fin de archivo file2.c 20, y permanece vigente slo durante la validez de func4 (lneas 38 a 45). As que la
asignacin de valores en las lneas 41 a 43 y la salida (lhea f) resultante de la lnea 44

Estructuras de datos en C lntroducci_n, a la estruc.tura de datos 65


64
se refiere slo a esas variables locales. Tan pronto como termina !unc4 (lnea 45) se Si la direccin de comil!nzo de p es 100, cules son las direcciones de comienzo (en
bytes) de?:
eliminan estas variables. Las referencias subsecuentes ax, y y z (lmea 29) se entien-
den como refere;icias a las variables globales x, y y z (lnea 1 Y 20), que producen la a. p[10l
b. p[20l ,name.midinit
salida de la lnea g. c. p[20J,income
d. p[20J .address[SJ
EJERCICIOS L p[SJ,parents[1J.last[10l
t.3.4. Supngase dos arreglos, uno de registros de estudiantes y el otro de empleados. Cada re-
1.3.1. Implantar nmeros complejos, tal y como se especific en el ejercicio 1.1.8, po.r ~edio gistro de estudiante contiene miembros para el nombre, el apellido y un ndice tje califi-
de estructuras con partes real y compleja. Escriba programas para sumar, mult1phcar Y cacin. Cada registro de empleado contiene miembros para el nombre 1, el apellido y el
negar dichos nmeros. salario. Ambos arreglos se ordenan en orden alfabtico por apellido y nombre. Dos re-
l.3.2. Supngase que un nmero real est representado .por u~a estructura en C como: gistros con el mismo nombre y apellido no pueden aparecer en el mismo arreglo. Escri-
ba una funcin en C para dar un 10% de aumento a todos los empleados que tengan un
struc,t realtype, registro de estudiante y cuyo indice de calificaciories sea mayor que 3.0.
int left; 1.3.5, Escriba una funcin corno en el ejercicio anterior. pero suponga que los registros del
.int right; empleado y el estudiante estn guardados en dos archivos externos ordenados en lugar
) ; de en dos arreglos ordenados.
. . 1.3.6. Por medio de la representacin de nmeros racionales dada en el texto, escriba rutinas
donde /eft y right representan los dgitos de la derecha y la izquierda del punto deci,mal, para sumar. restar y dividir dichos nmeros.
respectivamente. Si teft es un entero negativo, el nme~o real represent.ado es un nume.- 1.3.7. El texto presenta una funcin equal para determinar cundo son iguales dos nmeros
ro negativo. racionales rl y r2 por medio de probar igualdad entre las fracciones reducidas de am- -
a. E~criba u.na runa que acepte un. nmero real y cree una estructura QUf re~~es{!nte bos nmeros. Un mtodo alternativo puede ser multiplicar el numerador de cada uno
este nmero. por el denominador del otro para ver despu~s si los productos as obtenidos son
b. EStriba una funcin que ,acepte tal estruc,tiirO r regrese el nmero real repres~ntado iguales. Escriba una- funcin en C equa/2 para implantar este algoritmo., Cul_ . de los
p6r sta. ., . . . . , . . . dos mtorlos.e.s preferible?
c. Escfiba' rutinas add, ;,;;ubtract y multiply qu.e acepten dos estructur~s de ese ttpo Y
deri valor a una tercera estructura que repreSnte el nmero suma, diferencia Y pro-
ducto, respectivamente, de los dos registros de ntrada;
. .. . . . h . ' t l
1.3;3. Supllgase que un entero necesita cuatro bytes, un nmero real oc o Y un carac er
byte; Suptigas las sigu_ientes dclaraciones Y definiciones:

struct naetype 1
char first[10l;
char midinit;
char last[20l;
) ;

struct person 1
struct nametype. name;
int birthday[2J; ..
struct n~metype parehts[21;
int. income
int riumchildren;
char address[20J;
char city[10l;
char state[2];
) ;
sttuct person p[1DOJ;

Estructuras de datos en C Introduccin a la estructura de datos 67


66
2
F
E

D
.
e
Pilas B
A
Figura 2.1.1 Una pila y sus elementos.

se agregan nuevos elementos a la pila, stos deben colocarse encima de F, y si se


suprimen algunos, F ser el primero en ser borrado. Esto se indica tambin por las
lneas verticales, las cuales se extienden ms all de los elementos de la pila en direc-
cin del tope de la misma.
En la figura 2. l .2a la pila, se encuentra en el mismo estado que el presentado
en la figura 2.1. l. En la figura 2. l.2b se aade el elemento G a la pila. De acuerdo
con la definicin, hay slo un lugar donde ste puede colocarse: en el tope. El ele-
mento tope de la pila ahora es G. En las figuras c, d y e se agregan los elementos H, I
y J sucesivamente. Obsrvese que el ltimo elemento insertado (en este caso}) est
en el tope de la pila. Sin embargo, a partir de f, la pila comienza a disminuir cuando
Uno de los conceptos ms tiles dentro de la ciencia de la computacin es el de pila. al suprimirse J, y despus!, H, G, y F en forma sucesiva. En cada punto el elemento
En este captulo examinaremos dicha estructura de datos, en apariencia sencilla, Y tope de la pila vara, dado que cada supresin puede hacerse slo en el tope. El ele-
veremos por qu tiene un papel tan prominente en las reas de programacin Y len- mento G no pudo quitarse de la pila antes qu J, I y H se eliminaran. Esto ltimo
guajes de programacin. Definiremos el concepto abstracto de pila Y mostra:~mos ilustra el atributo ms importante de una pila: el ltimo elemento insertado en una
cmo puede convertirse en una herramienta concreta y vahosa para la resolucion de pila es el primero en eliminarse. As, J se elimina antes que I porque aquella se aa-
problemas. di primero. Por esta razn, en algunas casiones a las pilas se les conoce como
listas UFO -last-in, first-out- (ltimo en entrar, primero en salir).
2.1. DEFINICION Y EJEMPLOS Entre j y k, la pila dej de disminuir y comenz a las_ figuras recer nuevamente
cuando se aadi el elemento K. Sin embargo este crecimiento es momentneo, ya
Una pila es una coleccin ordenada-de elementos en la que pueden insertarse Y supri- que la pila disminuye a slo tres elementos en la figura n.
mirse por un extremo, llamado tope, nuevos elementos. Se puede representar una Ntese que no se puede distinguir la figura a y a la figura i si slo se observa el
pila como en la figura 2. l. l. .. . .. .. estado de la pila en las dos situaciones. En ambos casos sta contiene los mismos ele-
A diferencia del arreglo, la definicin de pila incorpora la msercion y supresion mentos en el mismo orden y con el mismo tope. No se mantiene un registro en la pila
de elementos de tal manera que sta es un objeto dinmico constantemente va- del hecho de que se aadieron y quitron cuat_ro elementos en ese tiempo. Asimismo,
riable. En co~secuencia, surge la pregunta: cmo cambia una pila? La definicin no hay forma de distinguir entre las figuras d y f, o entre j y l. Si se necesita un
especifica que un extremo de la pila se designa como el tope d~ la misma. Pueden control de los elementos intermedios que han estado en la pila, ste debe conservarse
agregarse nuevos elementos en el tope de la pila (en cuyo c~so este se mueve hacia en alguna parte; pues no se registra en la pila misma.
arriba para corresponder al nuevo elemento que ocupa la cima), o pu.eden qmtarse De hecho hemos tenido un amplio panorama de lo que en realidad se observa
los ele ..1entos que estn en el tope (y entonces el tope se mueve hacia abaio. para en una pila. La verdadera imagen de una pila e_st dada por una vista desde el tope
corresponder al nuevo elemento que se encuentra hasta arriba). Para responder la hacia abajo y no de una ojeada incidental. As, en la figura 2.1.2, no existen diferen-
pregunta: cul camino es hacia arriba? Tenemos que definir a uno de los e~tremos cias perceptibles entre las figuras h y o. En cada caso, el elemento tope es G. Aunque
de la pila como su tope; es decir, en qu extremo pueden colocarse o suprimirse ele- la pila de h no es igual que la de o, la nica manera de determinar esto es eliminar to-
mentos. En la figura 2. l. l se observa que el hecho que la F est fsicamente ms arri- dos los elementos en ambas pilas y compararlos de forma individual. Aunque hemos
ba que el resto de los elementos de la pila, implica que es el elemento tope actual. Si estado viendo una seccin transversal de las pilas para tener una mayor compren-

68 Pilas 69
sin, hay que sealar que esto es una libertad adicional y no existe ninguna clusula
que obligue a hacerlo as.

Operaciones primitivas

Los dos cambios que se pueden hacer en una pila reciben nombres especiales.
Cuando se aade a una pila un elemento, decimos que se agreg (push) en la pila y
cuando se suprime, decimos que se quit (pop) de la pila. Dada una pila S y un ele-
mento i, ejecutar la operacin push(s, i) aade el elemento i al tope de la pila s. De
manera parecida, la operacin pop(s) elimina el elemento tope y lo regresa como el
valor de la funcin. As, la operacin de asignacin

i = pop(s);

elimina el elemento en el tope de s y asigna su valor a i.


Por ejemplo, sis es la pila de la figura 2. 1.2, ejecutamos la operacin push(s,
G) para pasar de la figura a a la b. Luego realizamos en forma ordenada las siguien-
tes operaciones:

push ( s, H); ( figura (e) )


push ( s, I); ( figura <a>>
push ( s' J); ( figura (e) )
pop ( s) ; ( figura ( f) )
pop ( s) ; ( figura ( g))
pop ( s) ; ( figura ( h))
pop ( s) ; ( figura ( i) )
pop ( s) ; ( figura (j ) )
push ( s, K); ( figura ( k) )
pop ( s) ; ( figura (1))
pop ( s) ; ( figura ( m) )
pop ( S) ; (figura (n))
push ( s, G) ; ( figura (o) )

A una pila se le conoce con frecuencia como lista de apilamiento debido a la


operacin push, que aade elementos a la pila.
No existe lmite superior sobre el nmero de elementos que pueden almacenar-
se en una pila, dado que la definicin no especifict'cuntos elementos puede permi-
tir la coleccin. Poner un nuevo elemento en la pila tan slo ocasiona una mayor
coleccin. Sin embargo, si una pila contiene un solo elemento y se elimina, la pila
resultante no contiene elementos y se llama pila vacfa. Aunque la operacin push es
aplicable a cualquier pila, no ocurre lo mismo con la operacin pop que no puede
aplicarse a la pila vaca, que no tie~eningn elemento que eliminar. En consecu~n-
cia, antes de aplicar a una pila la operacin pop debe asegurarse de que no. est
vaca. Si lo est empty(s) determina si una pilas est o no vaca. Si lo est empty(s)
regresa el valor TRUE, de lo contrario regresa el valor FALSE.

Estructuras de datos en C
70 Pilas 71
,.~--
- -

Otra operacin que puede ejecutarse en una pila es la de determinar el valor del sin que se haya encontrado su pareja derecha. Se define el conteo de parntesis en un
tope de una pila sin eliminarlo. Esta operacin se escribe como stacktop(s) y regresa lugar determinado de una expresin, como el nmero de parntesis izquierdos me-
como resultado el elemento topes de la pila. La operacin stacktop(s) en realidad no nos el nmero de parntesis derechos que se han encontrado al recorrer la expresin
es una operacin nueva dado que puede descomponerse en las operaciones pop y push. desde su extremo izquierdo hasta el lugar determinado antes. Si el conteo de parnte-
sis no es negativo, coincide con la profundidad de anidamiento. Las dos condiciones
i = stacktop(.s) que deben cumplirse si los parntesis forman una pauta admisible en una expresin
son las siguientes:
es equivalente a
l. El conteo de parntesis es Oal final de la expresin. Esto implica que no se ha
i - pop(s);
push (s,i);
dejado abierto ningn mbito o que se encontraron tantos parntesis izquier-
dos como derechos.
2. El conteo de parntesis no es negativo en n'ingn lugar de la expresin. Esto
As como la operacin pop, stacktop no est definida para la pila vaca. El resultado implica que no se encontr ningn parntesis derecho para el cual no se
de un intento no permitido de accesar o eliminar un elemento de una pila vaca se hubiera encontrado_ con anterioridad su pareja izquierda.
conoce como subdesborde. Lo cual puede evitarse. al asegurarse de que empty(s) es
falsa antes de intentar la operacinpop(s) o stacktop(s). En la figura 2.1.3 se da el conteo de parntesis de forma directa abajo de cada
lugar en cada una de las cinco cadenas. Como slo la primera cumple las condi-
Un ejemplo ciones mencwnadas antes, es la nica que tiene una pauta correcta de parntesis.
Cambiemos ahora un poco el problema y supngase que existen tres tipos dife-
Ahora que ya se ha definido una pila y se han especificado las operaciones que rentes de delimitadores de mbito, los cuales se indican mediante parntesis ((y)),
pueden realizarse en ella, se ver cmo puede usarse una pila en la resolucin de corchetes ([y]) Y llaves, ((y}). El smbolo que cierra el mbito debe ser del mismo
problemas. Considrese una operacin matemtica que contenga varios conjuntos tipo que el smbolo que lo abre. As, cadenas como las siguientes no estn permiti-
de parntesis anidados, por ejemplo, das.

7 - ( (X * ((X+ Y) / (J - 3)) + Y) / (4 - 2.5)) (A + B], [(A + B]), {A - (Bl}

Quiere asegurarse de que los parntesis estn anidados en forma correcta; es decir,
quiere comprobarse:
7 - ( X ( ( X+ Y)/ I J - 3 ) ) +Y) / ( 4 - 2.5 ) )
t. Que hay el mismo nmero de parntesis izquierdos y derechos 00 22234444334444322211222 2 1 O
2. Que todo parntesis derecho est prec_edido por su pareja izquierdo.
I A+ B)
Expresiones del tipo 2 2 2 2

((A + B) o A+ B(
A+B
ooo
violan la condicin l y las del tipo

)A + B(-"C o . (A + B)) ~ (C +D A + B I -C
-1-1-1
violan la condicin 2.
Para resolver este problema, pinsese que cada parntesis izquierdo abre un A+B) -(C+D
mbito y cada parntesis derecho lo cierra. La profundidad de anidamiento en un lu: 1110-1-10000
gar particular de una expresin es el nmero de mbitos que se han abierto pero que
FifJura 2.1.3 Conteo de parntesis en vario$ puntos de la cadena.
an no se cierra. Es lo mismo que el nmero de parntesis izquierdos encontrados

72 Estructuras de datos en C Pilas


73
i '.i'.\
, .....iF
.. . ...

l',1. I. i Es necesario tener en cuenta no slo cuntos mbitos se abren sino tambin de
qu tipo son. Se necesita esta informacin porque cuando se encuentra un smbolo
que cierra un mbito, debe conocerse el smbolo con el que fue abierto para asegu-
rarse de que se ha cerrado en forma correcta.
Una pila puede usarse para rastrear la cantidad de smbolos de un tipo dado
que se han encontrado. Cuando se encuentra un smbolo que abre un mbito, don-
dequiera que ocurra, se coloca en la pila. Dondequiera que se encuentre un smbolo

l de cierre, se examina la pila. Si la pila est vaca, el smbolo que cierra no tiene su pa-
reja correspondiente y la cadena en cuestin no es vlida. Sin embargo, si la pila no
est vaca, extraemos elementos de .la misma por medio de pop y vemos si alguno IX+ ( ..

I (x+(y-[ ..
corresponde al smbolo de cierre encontrado. Si hay correspondencia continuamos;
si no la hay, la cadena no es vlida. Cuando se alcanza el final de la cadena, la pila
tiene que estar vaca, pues de otro modo se abrieron parntesis que luego no se cerra-
ron y la cadena no es vlida. El algoritmo para este procedimiento .se da a con-
tinuacin. La figura 2. l.4 muestra el estado de la pila despus de leer en partes la
cadena.

val id = true; /* se supone vli9'a la cadena *I


s = la pila vaca
while /nO hemos ledo toda la cadena)
lee 1 siguientes Smbolo (symb) de la cadena:
lx _+(y- (a+ hl {x+(y-[a+hl). tx+(y-[a+h))c-[( ...
i f ( symb == 1 ( 1 1 1 symb == 1 [ 1 1 1 symb == 1 ( )

push(s,symb);

i f ( symb == ) 1 1 symb 1 J1 1 1 symb == ) )


i f (empty(s))
valid = false;
else (
i = pop(s);
if (i no es el smbolo de inicio de symb)
valid = false;
I* end else *I
/* end while *f
i f ( ! empty( s))
!x :.. (y - ,, + h 1 * e -
J [ (d + d] ! l.\ + (y - [a + hj) e - [ (d +e)] )/(h ~ (j- (k - [ ...
valid = false;

i f (valid)
print!C'%s 11 1 "la cadena es vlida")
else
print("%s", "la cadena no es vlida");

Veamos por qu la solucin de este problema precisa el uso de una pila. El lti-
mo mbito abierto debe ser el primero en cerrarse, lo cual se simula mediante una pi-
la en la que el ltimo elemento que llega es el primero en salir. Cada elemento de la
pila representa ia apertura de uno de estos mbitos (llaves, parntesis o corchetes) {x+(y-[a+h])c [(d+e)J)/(h-(j(k-[1-,])) ... {x+(y-[a+bJ)c-[(d+e)J)f(h-(j-(k-[1-nl)))
que no se han cerrado todava. Poner un elemento dentro de la pila corresponde a
abrir un mbito y sacar un elemento de la pila corresponde a cerrar un mbito, y se
deja un mbito menos abierto. Figura 2.1,4 La pila-de parntesis en varios estados del procesamiento.

1,i 1
li, 74 Estructuras de datos en C, Pilas
ulli'I 75
lb
c. D~do un ent~ro ,.1, poner a i como el n-simo elemento a partir del tope de la pila
Ntese la correspondencia entre el nmero de elementos en la pila en este deJando la pila sm los n elementos tope. . '
ejemplo y el conteo de parntesis en el ejemplo anterior. Cuando la pila est vaca (y d. ~ad? un entero n, poner a i como el n-simo elemento a partir del tope dejando la
el conteo de parntesis es O) y se encuentra un smbolo de cierre de mbito, se intenta pila intacta. '
cerrar un mbito que nunca se haba abierto, de manera que la pauta de parntesis e. Poner a! como el elemento del fondo de la pila, dejando la pila vaca.
no es vlida. En el primer ejemplo, sto se indica por medio de un valor negativo del f. P~ner a I c?mo el .e!emento del fondo de la pila dejando la pila intacta. (Sugerencia:
conteo y en el segundo por la imposibilidad de eliminar un elemento de la pila. La usar una pila auxiliar extra.)
razn por la cual un conteo de parntesis simple es inadecuado para el segundo g: Poner a i cmo el tercer elemento a partir del fondo de Ja pila.
ejemplo, es que no se debe perder de vista el tipo de mbito abierto. Esto puede 2.1.2. Si~ule la accin de! algoritmo presentado en esta seccin para cada una de Jas siguien-
hacerse por medio de una pila. Ntese tambin que en cualquier momento, slo se tes cadenas, mostrando los contenidos de la pila en cada paso.
examina el elemento tope de la pila. La configuracin particular de los parntesis an- a.(A+B))
teriores al elemento tope es irrelevante mientras lo examinamos. Slo despus de eli- b. { [A + BJ [(C - D)]
c. (A + B) - { C + D) - [F + GJ
minar el elemento tope es que nos interesan los elementos subsecuentes de la pila.
d. ((H) * {([J + K])))
En general puede usarse una pila en cualquier situacin que se preste a un com-
e. (((A))))
portamiento de ltimo en entrar, primero en salir (last-in, first-out) o que presente 2. 1.3. !~riba un algoritm9 para determinar si uria cadena de caracteres de entrada es 1a for-,
una pauta de anidamiento. Se vern ms ejemplos del uso de pilas en las secciones
restantes de este capitulo y a lo largo del libro. X C y

La pila como un tipo de datos abstracto


d.onde x es una cadena constituida por las letras 'A' y 'B.' y y es el reverso de ( d
s1 x -- ''ABABBA"
. .
,' Y tiene .
que ser 1gual a "ABBABA"). En cada paso se xpuede es ectr,
slo
La representacin de una pila como un tipo de datos abstracto es directa. Usa-
leer el s1gmente caracter de la cadena.
mos e/type para denotar el tipo del elemento de la pila y parametrizamos el tipo de 2.1.4. Escriba un algoritmo para determinar si una cadena de caracteres de entrada es de la
pila con e/type. forma

abstract typedef <<eltype>> STACK (eltype); aDbDcD ... Dz

abstract empty(s) do~de cad~ cadena a, h, ... , z es de la forma de la cadena definida en el e)jercicio 2.1.3.
STACK (eltype)s; (_As1, una cadena es de la fo~ma apropiada si consiste en cualquier nmero de tales
(len(s) o) ;
postcondition empty ~ad~nas separadas por el caracter 'O'). En cada paso se puede leer slo el siguiente
caracter de la cadena.
abstract_eltype pop(s)
STACK (eltype) s; 2.1.5. Disee un alg~rit m~ que no use una pila para leer una secuencia de operaciones push y
preconditon empty(s) == FALSE; P~P Y dete~mmar s1 ocurre o no subdesborde en alguna oper:acin pop_._ Implntese
postcondition pop == first(s'); dicho algontmo en lenguaje c.
s == sub(s 1 , 1, len(s') - 1); 2. 1.6. Qu.~ c~njunto d~ condiciones son necesarias y suficientes para dejar una pila vaca y
~. caus~r. s.ubdesborde . usar una secuencia de operaciones puih y pop en una. f)iia
abstract push(s, elt) ~1.mrle (mic~almcntc ~acrn)? Qu conjunto de condicines son riecesarias en tal secuen~
STACK(eltype) s; ua para deJar una pila no vaciasin cambios?
eltype elt; 1
postcondition s==<elt> + s ;

2.2. REPRESENT ACION DE PILAS EN C

EJERCICIOS Antes de programar la. solucin. de un problema que use una pila , debed ec1dirse co-
'
mo :epresentar una P,''
mediante las estructuras de datos existentes en nuestro len-
2. l. t. Use las operal'.ioncs push, pop, stacktop y empt_v para construir operaciones que hagan guaje de programac'.? Como se ver, hay varias maneras de representar una pila
lo siguiente: en lenguaje C. C~ns,derese ahora la ms simple. A lo largo del libro, se presentarn
a. Poner a; como el segundo elemento a partir del tope, dejando la pila sin los dos ele- o_t;as rep'.esentac,~nes pos,_bles. Cada una, sin embargo, es tan slo una implanta-
mentos tope. c,on del concepto rntroduc1do en la seccin 2.1 que tiene ventajas y desventajas en
h. Poner a i como el segundo elemento a partir del tope, dejando 1~ pila intacta.

76 Estructuras de datos en C
Pilas 71
1,
' #define STRING 3
cuanto a la manera exacta en que refleja el concepto abstracto de pila y el esfuerzo struct stackelement
hecho por el programador y la computadora para usarla. int etype I* etype es igual a INTGR, FLT y STRING *I
U na pila es una coleccin ordenada de elementos; el lenguaje C ya incluye un I* dependiendo del tipo de *I
tipo de datos que es una coleccin ordenada de elementos: el arreglo. Por ello, es
I* elemento correspondiente *!
union {
tentador comenzar un programa con la declaracin de una variable pila como int ival;
arreglo. Siempr que la solucin de un problema requiera el uso de una pila. Sin float fval;
embargo, una pila y un arreglo son dos cosas por completo diferentes. El nmero de char *pval I* apuntador dela Cadena *I
elementos de un arreglo es fijo y se asigna mediante su declaracin. En general, el element;
usuario no puede cambiar dicho nmero. Por otra parte, una. pila es en lo funda-
mental un objeto dinmico cuyo tamao cambia constantemente en tanto se le agre, };
gan o quitan elementos. struct stack {
Sin embargo aunque un arreglo no puede ser una pila, puede usarse como tal. int top;
Es decir, puede declararse un arreglo lo bastante grande para admitir el tamao m- struct stackelement items[STACKTOPl;
};
ximo de la pila. Durante la ejecucin del programa, la pila puede crecer ycbntraerse
dentro de su espacio reservado. El fondo fijo de la pila es un extremo del arreglo,
mientras que su tope cambia en forma constante cuando se agregan o quitan elemen- define una pila cuyos elementos pueden ser enteros, nmeros de punto-flotante o ca-
tos. Asi, es necesario otro campo para que en cada paso de la ejecucin del progra: denas, dependiendo del valor correspondiente de eltype. Dada una pilas, declarada
por
ma, registre la posicin actual del elemento tope de la pila.
En consecuencia, puede declrarse una pila en C, como una estructura que struct stack s;
contiene dos objetos: un arreglo para guardar los elementos de la pila Y un entero
para indicar la posicin del elemento tope actual dentro del arreglo; lo cuaLpuede se puede imprimir su elemento tope como sigue:
hacerse para una pila de enteros por medio de las declaraciones:
struct stackelement se;
#define STACKSIZE 100
struct stack { se= s.items[s.top];
int top; switch (se.etype) {
int items[STACKSIZE]; case INTGR printf(''' d\n 11 , se.ival);
}; case FLT printf( 11 % f\n 11 , s~ .. fval);
case STRING printf( 11 %. s\n 11 , s~.~val);
Una vez hecho, puede declararse una pilas, por medio de: I * fin de switch */
Para simplificar, supondremos en el resto de esta seccin que una pila slo
struct stack s;
contiene elementos homogneos (de maher que fas uniones no son necesarias). El
identificador tope debe declararse siempre con entero, dado que su valor representa
Aqui, se supone que son enteros los elementos de la pilas contenidos en el _arreglo la posicin del elemento tope de la pila dentro de los elementos del arreglo items.
s.items y que la pila nunca contiene ms de STACKSIZE enteros,;En este eJemplo, Por elle); si el valor des.top es 4, hay 5 elementos en la pila: s.items[O], s.items[I],
STACKSIZE se fija en 100 para indicar que la pila puede contener 100 elementos s.items[2], s.items[3] y s.items[4]. Cuando se elimina un elemento de la pila cambia
(desde items[O] hasta items[99]). . el valor de s. top a 3 para indicar que ahora slo hay 4 elementos en ella y que
Por supuesto, no hay razn para restringir una pila que slo contenga enteros; s. items[3] es el elemento tope. Pr otra. parte, si se aade un nuevo objeto a la pila, el
los elementos items podran declararse fcilmente coniofloat items[STACKSIZE] o valor de s,top debe incrementarse en 1 para convertirse en 5 y el nuevo objeto, tiene
char items[STACKSIZE] o cualquier otro tipo que se quiera dar a los elementos de que insertarse dentro de s.items[5].
la pila. De hecho, en caso de surgir la necesidad, una pila puede contener objetos de La pila vaca no contiene elementos y puede, en consecuencia, indicarse me-
diferentes tipos al usar uniones en C. As diante top igual a -1. Para inicializar una pilas en el estado vaca, puede ejecutarse
desde el inicio s. top = --'-l;.
#define STACKSIZE 100 Para determinar durante la ejecucin, si una pila est o no vaca, se verifica la
#define INTGR 1 condicin s. top = = - i"en una instruccin if, tal y como sigue:
#define FLT 2

Pilas 79
78 Estructuras de datos en C
if (s.top -- -1) ficativa que la frase "s.top = = -1". Si se introdujera despus una implantacin
I * la pila est vaca *I mejor de pila, de tal manera que "s.top = = -1" careciera de significado, tendran
else que cambiarse todas las referencias al identificador de campo s.top en todo
I* la pila no est vaca *I programa. Por otra parte, la frase "empty(&s)" tendr an su significado, dado
que es un atributo propio del concepto de pila y no de su implantacin. Todo lo que
Esta verificacin corresponde a la operacin empty(s) que se introdujo en la seccin se precisara para revisar un programa con el objetivo de acomodar una nueva
2.1. De otro modo, puede escribirse una funcin que d como resultado TRUE (ver- implantacin de pila sera una posible revisin de la declaracin de la estructura
dadero) si la pila est vacia y PAL.SE (falso) en caso contrario, como sigue: stack en el programa principal y la reformulacin de empty. (Tambin es posible que
la forma de llamar a empty tenga que modificarse de manera que no use ninguna
empty(ps) direccin.)
struct stack *ps; Un mtodo importante para hacer un programa ms claro y modificable es el
{ de reunir todos los sitios dependientes de la implantacin en unidades pequeas y f-
if (ps->top -1) cilmente identificables. Este concepto se conoce como modularizacin, por el cual se
return (TRUE); aislan funciones individuales en mdulos de bajo nivel, cuyas propiedades son fci-
else les. de verificar. Esos mdulos de bajo nivel pueden usarlos despus rutinas ms
return(FALSE)
complejas, que no deben tratar los detalles de los mdulos de bajo nivel sino sus
I * fin de empty *I funciones. A las mismas rutinas complejas pueden tratarlas despus como mdulos
rutinas de nivel mayor, que las usan de modo independiente de sus detalles internos.
Una vez que existe la funcin, se implanta la verificacin para la vacuidad de la pila Un programador debe interesarse siempre por la claridad de lo que escribe. Un
mediante la siguiente instruccin: mnimo de atencin a la claridad ahorrar mucho tiempo en la bsqueda y depura-
cin de errores. Los programas medianos y grandes casi nunca sern del todo corree~
1f (empty (&s)) tos cuando se corren por primera vez. Si se toman precauciones desde el momento en
/ * la pila est vaca * / que se escribe, para asegurar que sea fcil de modificar y comprender, el tiempo
else total necesario para lograr que corra en forma correcta, se reducir considerable-
I* la pila no est vaca *I mente. Por ejemplo, la instruccin if en la funcin empty podra remplazarse por la
ms corta y eficiente:
Ntese que la diferencia entre l.a sintaxis de la llamada de empty en el algoritmo
de la seccin previa y en el segmento de programa aqu presentado. En el algoritmo, return (ps->top = -1);
s representaba una pila y l Ifamada a empty fue expresada como
Esta instruccin es la misma que la instruccin ms grande:
empty(s)
if (s.top ==-1)
En esta seccin nos interesa la implantacin real de una pila y sus oper,ciones. Co- return (TRUE);
mo en el lenguaje C, los parmetros se transfieren por valor, la nica manera de mo' else return (FALSE);
dificar el argumento transferido a una funcin, es pasar la direccin del argumento
en lugar del argumento. Adems, la definicin original de C (por Kernighan y
Esto ocurre porque el valor de la expresin s.top ~ ~ -1 es TRUE si, y slo si la
Ritchie) y muchos compiladores C ms antiguos, no permiten pasar una estructura
condicin s.top = = -1 es TRUE. Sin embargo, quien lea el programa lo har pro-
como argumento aun si su valor permanece inalterado. As; en funciones comopop
bablemente en forma ms sencilla al leer la instruccin if. Con frecuencia se des-
y push (que modifican sus argumentos estructurales) as como empty (que no lo ha-
cubrir que. al usar "trucos" de lenguaje al escr.ibir programas, es imposible
ce), se adopta la convencin de transferir la direccin de la estructura pila, en lugar
descifrarlos despus de haberlos abandonado por uno o dos das,
de la pila misma. (Esta restriccin se ha omitido en muchos compiladores nuevos.)
Aunque es cierto que un programador de lenguaje C tiene, con frecuencia que
Puede.resultar asombroso que los autores del presente libro se ocupen de defi-
ocuparse de la 'economa del programa, tambin es importante considerar el tiempo
nir la funcinempty cuando pudiera tan s>lo escribirse ifs.top ~ ~ -1, cada vez
que se emplear sin duda en la bsqueda y depuracin de errores. El profesional
que se quisiera verificar la condicin de vacuidad. La razn es que se desea hacer
maduro (sea en C o en cualquier otro lenguaje) se interesa siempre por el balance
ms comprensibles los programas y hacer independiente de su implantacin el uso de
apropiado entre la economa y la claridad de un programa.
una pila. Una vez entendido el concepto de pila, la frase "empty(&s)" es ms signi"

80 Estructuras de datos en C Pilas. 81


Implantacin de la operacin pop Esto es cierto a pesar de que ps - > items[87] conserva su antiguo valor.
, Para usar la funcin quitar, el programador puede declarar nt x y escribir
En la implantacin de la operacin de pop, tiene que considerarse la posibi-
lidad de subdesborde, dado que el usuario puede intentar, sin saberlo, sacar un x=pop(&s);
elemento de una pila vaca. Por supuesto que tal intento no est permitido y debe
evitarse. Sin embargo, si se hiciera se informara al usuario de la condicin de des- entonces x contiene el valor eliminado de la pila. Si el propsito de la operacin pop
borde. En consecuencia se presenta una funcin pop que ejecuta las tres acciones si- no es recuperar el elemento del tope de la pila sino eliminarlo, el valor de x no se vol-
guientes: ver a usar en el programa.
Por supuesto, el programador debe asegurarse de que la pila no est vaca
l. Si la pila est vaca, imprime un mensaje de advertencia Y.detiene la ejecucin. cuando llama la operacin pop. Si no est seguro del estado de la pila, puede deter-
2. Suprime el elemento tope de la pila. minarse al programar
3. Regresa el elemento al programa de llamada.
if (!empty(&s))
Se supondr que la. pila consiste en enteros, de tal manera que la peracin pop x = pop ( &s);
else
puede implantarse como funcin, lo cual tambin sucedera si la pila consiste en al- /* efecte una accin de correccin *I
gn otro tipo de variable simple. Sin embargo, si la pila est compuesta. ppr estrutu;
ras ms complejas (por ejemplo, una estructura o una unin), la operacin pop se
implantara al regresar como resultad.o o un apuntador al elemento del tipo ade- Si el programador llama sin percatarse a pop para una pila vaca, la funcin
cuado (en lugar del elemento mismo) o el valor eliminado de la pila como parmetro. imprime el mensaje de error subdesborde y se detiene la ejecucin. Aunque es algo
(en cuyo caso la direccin del parmetro transferira en lugar del parnietro, de tal desafortunado, es mejor de lo que ocurrira si se hubiese omitido por completo la
manera que la funcin pop modificase el argumento real). instruccin f en la rutina pop. En ese caso, el valor des.top sera -1 y se intentara
dar aceso al elemento no existente s.ilems[-1].
pop(ps) El programador deber estar prevenido ante la posibilidad casi segura de error;
struct stack *ps; lo cual puede hacerse al incluir diagnsticos significativos en el contexto del proble-
{ ma. Al hacerlo, el programador podr seala.r con precisin el origen de los errores y
if (empty(ps)) emprender acciones .correcti.vas de. manera inmediata, si ocurren-.
printf("%s", "subdesborde" );
exit(1); Verificacin de condiciones excepcionales
!* fin deif *I
return(ps->items[ps->top--J) En el contexto de un cierto problema, puede no ser necesario detener la ejecu-
l*finde pop*! cin de manera inmediata al detectar subdesborde. En su lugar, podra ser ms con-
veniente para la rutina pop sealar al programa de llamada que esto ha ocurrido. La
rutina de llamada al detectar dicha seal puede tomar la accin correctiva. Se llama-
Ntese que ps ya es un apuntador a una estructura de tipo pila; de ah que el r al procedimiento que elimina n elemerrt-0 de la pila y regresa una indicacin si ha
operador"&" no se usa para llamar a empty. En.todas las aplicaciones en C, tiene ocurrido subdesborde: popandtest:
que distinguirse siempre, entre apuntadores y objetos de datos reales.
Veamos con mayor detalle la funcin pop. Si.la pila no est v~ca, se retiene el popandtest(ps,,px, pund)
elemento tope de la misma como valor resultante. Despus este elemento se saca de struct ~tack *ps;
la pila mediante la expresin ps T" > top ,,--, Supngase que cuando se llama. a int *pund, .*px;
pop, ps - > top es igual a 87;, es decir, hay 88 elementos en la pila. El valor de {
if (empty(ps)) {
ps - > items[87] se regresa y el valor .ele ps - > top se cambia a 86 . Obsrvese que
*pund = TRUE;
ps - > ilems[87] an retiene su antiguo valor; el arreglo.ps - > items permanece return;
sin cambio por la llamada apop. Sin embargo, se modifica la pila, ya que ahora slo )!* findeif *!
contiene 87 elementos en lugar de 88. Recurdese que una pila y un arreglo son dos *pund = '.FALSE;
objetos diferentes. El arreglo slo proporcipna,un Jugar para la pila. En s. la pila ,// .
*px = ps->1tems[ps->to~--J;
contiene slo aquellos elementos entre el O y el top 0simo del arreglo. As, la reduc- return;
cin en I del valor de ps - > top, elimina de manera efectiva un elemento de la pila. /* fin de popandtest *I

82 Estructuras de datos en C Pilas 83


En el programa de llamada el programador escribira push(ps,x)
struct stack *ps;
popandtest(&s, &x, &und); int x;
if (und)
i! {ps->top -- STACKSIZE-1)
I * efecta una accin correctiva */ printf("%s" 1 "desborde de la pila");
else
I * usa el valor de x- */ exit{1);

else
ps->items[++(ps->top)l x;
Implantacin de la operacin push
return;
!* fin depush *I
Examinemos ahora la operacin push. Tal parece que esta operacin puede ser
muy fcil de implantar al usar la representacin por arreglo de una pila. Un primer Aqu, se comprueba si el arreglo est lleno antes de intentar aadir otro elemento a
intento para el procedimiento push podra ser el siguiente: la pila. El arreglo est lleno si ps - > top.= = stacksize ~1.
Dbe observarse de nuevo que siempre y cuando se detecte desborde en push,
push(ps, x) se detiene de manera inmediata la ejecucin despus de imprimir un mensaje que in-
struct stack *ps; dica el error. Esta accin, como en el caso de pop, puede no ser la ms deseable. En
int x; algunos casos podra tener ms sentido para la rutina de llamada invocar la opera-
{ cin push con las siguientes instrucciones
ps->items[++(ps->top)l x;
return; pushandtest(&s, x, & overflow);
I* fin depush *I i! {overflow)
I* se ha detectado desborde, no se coloc *I
Esta rutina hace lugar al elemento x que se agregar a la pila al incrementar s.top en I * x en la pila. Ejecute accin correctiva *I
else
l, y despus inserta x en el arreglo -~>items. se coloc exitosamente a x en la pila *I.
I*
La rutina implanta de manera directa la operacin push presentada en la ltima I* contine 'el procesamiento . *I
seccin. Sin embargo, tal y como se dedar es del todo incorrecta. Permite que se
presente un sutil error causado por el empleo de la representacin por arreglo de la
pila. Recurdes.e que una p'la es una estructura dinmica que puede en forma cons- Esto permite al programa de llamada proceder despus de la llamada a pushandtest
tante crecer_ o contraerse y .cambiar as su tamao. Por otro. lado, un arreglo es un tanto si se detect o no desborde. La subrutina pushandtest se deja como ejercicio al
objeto fijo de tamao predeterminado, as que es muy concebible que una pila crez- lector.
ca ms que el arreglo.reservado pata almacenarla, lo cual ocurre cuando el arreglo Aunque las condiciones de desborde y subdesborde se han tratado de manera
est lleno, es decir, cuando la pila contiene tantos elementos como el arreglo y se in- similar enpush y pop, existe una diferencia fundamental entre ellas. El subdesborde
tenta aadirle uno ms. El resultado de dicho intento se llama desborde. indica que la operacin pop no puede ejecutarse en la pila, al sealar un posible
Supngase que el arreglo s.tems est lleno y se llama la rutina de C push. Re- error en los datos o en el algoritmo. Ninguna otra implantacin o representacin de
curdese que la primera posicin del arreglo es O y el tamao arbitrario elegido la pila resolver la condicin de subdesborde. En su lugar, debe reconsiderarse el
(STACKSIZE) para el arreglo s.items es 100. La condicin s.top =,= 99 indica en- problema por completo. (Por supuesto, podra ocurrir subdesborde como una seal
tonces que el arreglo est lleno, de tal manera que la posicin 99 (el 100 elemento del de culminacin de un proceso e inicio de otro. Pero en tal caso, debe usarse popand-
arreglo) es el elemento tope en ese momento. Cuando se llama a push, s.top se test en lugar de pop).
aumenta a 100 y se intenta insertar x en s.items[lOOJ. Por supuesto, el lmite superior El desborde, sin embargo, no es una condicin aplicable a una pila como
de s.lems es 99 y el intento de insercin conduce a un error impredecible, dependien- estructura de datos abstracta. De manera abstracta, es posible siempre poner un ele-
do de las localidades de memoria que siguen a la ltima posicin del arreglo. Puede mento en la pila. Una pila slo es un conjunto ordenado y no hay lmite para el n.-
producirse un mensaje de error probablemente no relacionado con su causa. mero de elementos que dicho conjunto puede contener. La posibilidad de desborde
En consecuencia, el procedimiento push debe revisarse de manera que se lea se presenta cuando se implanta la pila por medio de un arreglo que contiene un n-
como sigue: mero finito de elementos, con lo cul se prohibe el crecimiento de la pila por encima
de dicho nmero. Puede suceder que el algoritmo que usa el programador sea

84 Estructuras de datos en C Pilas 85


correcto, slo que su implantacin no anticipa que la pila puede llegar. ser tan _gran- Segunda, permite al programador usar la pila sin preocuparse por su modelo inter-
de. As que, en algunos casos la condicin de desborde se puede corr~gir cambiando no. Tercera, si se presenta una nueva implantacin de la pila, el programador no
el valor de la constante STA CKSIZE, de tal manera que el arreglo ,tems comenga
necesita recorrer todo el programa para buscar en donde aparece s.items[s.top] cam-
ms elementos. No es necesario cambiar las rutinas pop o push, dado que se refieren
bindolas para hacerlas compatibles con la nueva implantacin. Slo sera necesario
a cualquier estructura de datos que se haya declarado para el tipo stack en las decla-
cambi.ar- la rutina stacktop.
raciones del programa, push tambin se refiere a la constante STA CKSJZE Y no al
valor 100.

Sin embargo, es ms frecuente que un desborde indica un error en el pro~ra- EJERCICIOS


ma no atribuible a una simple falta de espacio. El programa puede caer en un ciclo
inflnito en el cual con frecuencia se afiaden elementos a la pila sin que se elimine na- 2.2. t. Escriba funciones en C que usen las rutinas presentadas en este captulo para implantar
da. As, la pila crecer ms que el lmite del arreglo sin importar qu tan _alto se haya las operaciones ~el ejercicio 2.1.1.
fijado. El programador siempre debe verificar que no suceda lq ante~10r antes,~e 2.2.2. Dada una secuencia de operaciones push 'y pop y un entero que representa el tamao de
aumentar en forma indiscriminada el lmite del arreglo. Con frecuencia el !amano un arreglo en el cual se implantar una pila, disee un algoritmo para determinar si
mximo de. la pila puede determinarse .con facilidad a partir del programa Y sus ocurre o no desborde; E,l algoritrrO no debeusar la pila. Implante el algoritmo como un
entradas, de tal manera que si ocurre desborde, quizs hay algo equivocado con el programa en C.
algoritmo que representa el programa. . . 2.2.3. Implante los algoritmos de los ejercicios 2.1.3 y 2.1.4 en programas C.
Veamos ah.oranuestrn ltima operacin con pilas, stacktop(s) que.regresa sm 2c2.4.' Muestre cmo implantar una pila de enteros en C por medio del arreglo int s [STACK-
eliminarlo el elemento tope de una pila. Como se sefial en la ltima seccin, stack- Sl.ZEJ, dond s[O]se usa para contener el ndice del elemento tope, y s[l] h'asta
top en realidad no es una operacin primitiva porque puede descomponerse en las s[STACKSIZE -1] para contener los elementos en la pila. Escriba una declaracin y
dos operaciones: rutinas pop, push, etrlpty, f;opandtest, stacktop y pushandtest para dicha-implanta-
cin.
x = pop(s); 2.2.5. Iinplante una pila en C en la cual cada eleme'rito es un nmero variable de enteros: Se-
push (s,x); leccione una estructura de datos de C para la pila y disee rtitihas push y pop para lla.
2.2.6. ConSidere un lenguaje que no tenga arreglos pero s piras coI!l tipo d datos. Es decir;
Sin embargo, es una manera ms bien torpe de recuperar el elemento tope de la se puede declarar
pila. Por qu no ignorar la descomposicin anterior y recuperar en forma drec;. el
valor apropiado? Por supuesto, tendra entonces que plantearse de maner~ exp!i~ita stack s;
una verificacin de vacuidad y subdesborde, dado que la prueba no est mas dmgida
por unaJlamada a pop. . . . . . . y estn definidas las operaciones push, pop, popandtest y stacktop. Muestre cmo
Presentamos una funcin C stacktop para una pila de enteros de la sigmente puede implantrse un arrCgI unidil11ension'al mediante dichas operaciones en dos pilas.
manera: 2.2. 7. Disefte un mtodo para guardar dos pilas dentro de un arreglo lineal simple $[spacesize]
de tal manera que no ocurre ningn desborde mientras ~o se haya usado toda la memo-
stacktop(ps) ria y una pila entera nunca sedesplaza a una localidad de memoria diferente dentro del
struct stack arreglo. Escriba rutinas en C: push 1, push2, pop 1 y pop2 para manipular las dos pilas.
{ (Sugerencia: las dos pilas crecen en direccin a la otra.)
1f (empty(ps)) { .. . . . 2,2.8. El estacionamiento Bashemin tiene un solo carril para estacionar hasta 10 coches. Hay
printf(''_%s", 11 subdesbOide de la pila" ) ; -.una sola entrada/saitcta en un extremo del carril.'Si ega un cliente a recoger su Coche y"
exit(l); ste no es el ms prximo a la salida, se sacan todos los coches que bloquean Sll'paso, se
saca el coche del cliente en cuestin y el resto se vuelve a estacionar en el orden original.
else Escriba un programa que procese un grupo de lneas de entrada. Cada lnea de
rturn{ps~>items[ps->topl); entrada contiene una 1 E' para la entrada, una 1 S' para la salida y un nmero de placas .
.} /* fin de stacktop *I Supngase que los coches llegan y salen en el orden especificado por la entrada. El
programa debe imP,rimir un mensaje siempre que un coche enire o salga. Cuando llega
. Puede preguntarse por qu se molesta en escribir una ruti~a separada p~ra un coche, el mensaje debe especificar si hay lugar en el estacionarriiento. Si no lo hay, el
stacktop, cuando una referencia a s.items[s.top] servira para lo mismo: Hay.':'anas coche no entra. Cuando un coche se va, el mensaje debe.incluir el nmero de veces que
razones para hacerlo. Primera, la rutina stacktop incorpora una venficac1on de fue sacado del estacionamiento para permitir la salida de otros.
subdesborde, de tal manera que no ocurran errores misteriosos si la pila est vaca.

86 Estructuras de datos en C
Pilas 87
2.3. UN EJEMPLO: NOTACION INFIJA,PREFIJA Y POSTFIJA En este ejemplo la suma se convierte antes que la multiplicacin dada la presencia de
parntesis. Para pasar de (A + B) * Ca (AB +) * C, los operandos son A y By + es
Definiciones bsicas y ejemplos el operador. Para hacerlo de (AB +)*Ca (AB + )C *, (AB +) y C son los operan-
Esta seccin examina una aplicacin fundamental que ilustra los diferentes ti- dos y* el operador. Las reglas para pasar de notacin infija la postfija son sencillas,
pos de pila y las diversas operaciones y funciones definidas sobre ellas. El ejempl? es a condicin de que se conozca el orden de precedencia.
tambin por derecho propio un tema importante en la c1enc1a de la computac10n. Considrese dnco operaciones binarias: suma, resta, multiplicacin, divisin y
Considrese la suma de A y B. Se piensa en la aplicacin del operador"+" a exponenciacin. Las cuatro primeras estn disponibles en C y se denotan por los
los operandos A y By en escribir la suma como A + B. Esta representacin en parti- operadores comunes +, -, *, y/. La quinta, la exponenciacin, se representa me-
cular se llama infija. Hay otras dos notaciones para expresar la suma de A Y B diante el operador $. El valor de la expresin A $Bes A elevada a la potencia B, as
que 3 $ 2 es 9. Para estos operadores binarios el orden de precedencia es el siguiente
mediante los smbolos A, B y +, las cuales son:
(de arriba hacia abajo):
+ AB prefija
AB.+ postfija
exponenciacin
Los prefijos 'pre-", "post-" e "in-" se refieren a la posicin relativa del ope-
rador respecto a los dos operandos. En la notacin prefija el operador precede a los multiplicacin/ divisin
dos operandos, en la postfija los sucede y en la infija est entre ambos. Las nota-
ciones prefija y postfija no son en realidad tan difciles de usar como podra parecer suma/resta
en un principio. Por ejemplo, una funcin en C que regresa la suma de los dos argu-
mentos.A y Bes llamada por add(A, B). El operador add precede a los operados A y B.
Consideremos otros ejemplos. La evaluacin de la expresin A + B * C, tal y Cuando se analizan operadores de la misma precedencia sin parntesis, se su-
como se escribe en notacin in fija estandard, requiere que se conozca cul de las dos pone que el orden es de izquierda a derecha excepto en el caso de la exponenciacin,
operaciones, + o_*, debe ejecutarse primero. En el caso de + y* "sabemos" que la donde se supone de derecha a izquierda. As, A .. B + C significa (A + B) + C
multiplicacin es prioritaria (en ausencia de parntesis que indiquen lo contrario). mientras que A $ B $ C significa A $ (B $ C). Al usar parntesis se puede obviar la
As, A + B C se interpreta como A + (B * C) a menos que se especifique otra precedencia.
cosa. Decimos que la multiplicacin tiene precedencia sobre la suma. Supngase que Se dan los siguientes ejemplos de conversin in fija a postfija. Asegrese de en-
se desea volver a escribir A + B * C en postfija. Al aplicar las reglas de precedencia, se tender cada uno de ellos (y de que puede realizarlos) antes de proceder con el resto de
convierte primero la porcin de la expresin evaluada en primer lugar, es decir, la la.seccin.
multiplicacin. Al hacer esta conversin en etapas
A + (B * C) parntesis para hacer hincapi
In fija Postfija
A + (BC ) conversin de la multiplicacin
A+B AB +
A (BC ) + conversin de la suma
A+B-C AB + C -
ABC + forma postfija
(A + B) * (C - D) AB+CD *
Las nicas reglas que hay que recordar durante la conversin son que primero A $B *C - D + E / F / (G + H) AB $ C * D - EF / GH + / +
se convierten las operaciones de mayor prioridad y luego de que se ha convertido
una porcin de la expresin a postfija debe tratarse como un operando simple. Con- ((A + B) * C - (D - E)) $ (F + G) AB + C * DE - - FG + $
sidrese el mismo ejemplo invirtiendo la precedencia de los operadores por medio de A -- B/(C * D $ E) ABCDE $ * / -
la insercin deliberada de parntesis.

(A + B) C forma infija
Las reglas d.e precedencia para convertir una expresin de in fija a prefija son
(AB +) *C conversin de la suma idnticas. El nico. ambio de la conversin a postfija es que el operador se coloca
antes que los operandos y no despus. Se presentan las formas prefijas de las expre-
(AB +) C * conversin de la multiplicacin
siones anteriores. Una vez ms, el lector debe intentar hacerlo.
AB + C * forma pos1fija

Estructuras de datos en C Pilas 89


88
Prefija mos. extraer dichos elementos, ejecutar la operacin indicada y poner el resultado en
Infija
la pila de tal manera que est disponible para usarse como operando del prximo
A+B + AB operador. El s1gmente algontmo evala una expresin en notacin posfija mediante
- + ABC este mtodo:
A+B-C
(A + B) (C - D) +AB-CD opndstk = la pila vaca ;
A $B *C - D + E / F / (G + H) + - * $ABCD / / EF + GH I * recorre la cadena de entrada leyendo un */
I * elemento a la vez y colocndolo en symb *I
((A + B) * C - (D - E)) $ (F + G) $- + ABC - DE + FG while (noo se detecte el final de la entrada) }
symb = siguiente caracter en la entrada ;
A - Bi(C D $ E) - Al B * C $ DE if (symb es un operando)
push(opndstk,' symb>;
Obsrvese que la forma prefija de una expresin compleja no es la imagen especular else {
de la forma postfija, como pued verse a partir del segundo de los ejemplos ante- I* symb es un operador */
riores, A + B - C. De aqu en adelante slo se considerarn las transformaciones a opnd2 = pop( opndstk),;
postfija y dejaremos como ejercicio al lector la mayor parte del trabajo que invo- opnd1 = pop(opnastk);
= valu~ res~ltado de aplicar sy'mb a opndi y opnd2;
lucra la prefija. push(opndstk, value);
Un detalle obvio de modo inmediato acerca de la notacin postfija es que no
} I* findeelse *I
requiere parntesis. Considrense las dos expresiones A + (B * C) y (A + B) * C.
I* fin de wbile *I
Aunque los parntesis en una de las dos son superfluos [(por convencin A + B * C return(pop(opndstk));
= A + (B + C)J, los de la segunda expresin son necesarios para evitar confusin
con la primera. Las formas postfijas de estas expresiones son
', Consideremos un ejemplo. Supngase que se pide evaluar la siguiente expre-
Postfija sin postfija: ' , ' , ,
In fija
A+(BC) ABC * + 623+-382/+*2$3+
(A+ Bj *C AB +C* Mostramos los contenidos de la pila opndstk y las variables symb, opndl, opnd2 y
No hay parntesis en ninguna de las dos expresiones transformadas. El orden vale despus de cada itercin sucesiva del ciclo. El tope de opndstk est a la de-
de los operadores en las expresiones postfijas determina el orden real de las opera- recha.
ciones en la evaluacin de la expresin, haciendo innecesario el uso de parntesis.
Para pasar de la forma in fija a la postfija se sacrifica la capacidad de notar de
symb opndl opnd2 vlue opndstk
un vistazo los operandos relacionados con un operador en particular. Sin embargo,
se gana una forma no ambigua de la expresin original al eliminar el uso de parnte- 6 6
sis incmodos. En realidad, la forma postfija de l: expresin original se vera ms 2 6;2
3 6,2,3
sencilla si no fuera por el.hecho de que parece difcil de evaluar. Por ejemplo, cmo 6,5
+ 2 3 5
sabemos que si A = l, B = 4 y C = 5 en los ejemplos anteriores, entonces 2 4 5 * es 6 5 1
igual a 23 y 3 4 + 5 * a 25? 3 6 5 1,3
8 6 5 1 1,3,8
2 6 5 1 1,3,8,2
Evaluacin de una expresin postfija
I 8 2 4 1,3,4
+ 3 4 7 1,7
La respuesta a la pregunta anterior se encuentra en el desarrollo de un agorit- 1 7 7 7,.
*
mo para evaluar expresiones dadas en notacin postfija. Cada operador en una ca- 2 1 7 7 7,2
dena postfija se refiere a los dos operandos previos en la'cadena. (Por supuesto, uno $. 7 2 49 49
de ellos puede ser el resultado de la aplicadn previa de un operador.) Spngase 3 7 2 49 49,3
+ 49 3 52 52
cue 'cada vez qu se lee un operando se pone dentro de una pila. Cuando se alcance
un operador, sus operandos sern los dos elementos tope de la pila. Entonces pode-

Estructuras de datos en C 91
90
Cada operando se inserta en la pila cuando se encuentra. En consecuencia, el #define MAXCOLS 80
main()
tamao mximo de la pila es el nmero de operandos que aparecen en la expresin {
de entrada. Sin embargo, al tratar con la mayor parte de las expresiones postfijas, el char expr[MAXCOLSJ;
tamao real necesario para la pila es menor que este mximo terico, dado que un int position = O;
operador saca elementos de la misma. En el ejemplo previo la pila nunca contiene float eval();
ms de cuatro elementos, a pesar de que aparecen ocho operandos en la expresin
postfija. while ( (expr[position++l - getchar ()) !- In);
expr[--positionJ -= '\0 1 ;
1
printf(' %s%s", \\la expresin postfija original es ",
expr);
Programa para evaluar una expresin postfija prin:1:,f(tt.%f\n", eval(expr));
I* fin de main *I

Por supuesto, la parte principal del programa es la funcin eval, que se presen-
Existen varias preguntas que deben considerarse antes de poder escribir en ta a continuacin. Esta funcin no es ms que la implantacin en C del algoritmo de
realidad un programa para evaluar una expresin en notacin postfija. U na conside- evaluacin, tomando en cuenta el formato y el medio especfico de los datos de
racin primaria, como en todos los programas, es definir con precisin la forma y entrada Y de las salidas calculadas. eva/ llama a una funcin isdigit que determina si
las restricciones, si las hay, en la entrada. Con frecuencia al programador se le pre- su argumento es o no un operando. La declaracin para una pila, que aparece abajo
senta la forma de la entrada y se le pide disear un programa para acomodar los la usa la funcin eva/ que la sigue, as como las rutinas pop y push que son llamadas
datos proporcionados. Por otra parte, estamos en la afortunada situacin de poder es- por eva/.
coger la forma de nuestra entrada. Esto permite construir un programa que no est struct stack {
sobrecargado con problemas de transformacin que opaquen el objeto real de la ru- int top;
tina. Al habernos enfrentado con datos en una forma, con la cual es difcily float items[MAXCOLS];
};
engorroso trabajar, se podra haber relegado las transformaciones a varias fun-
ciones y usar las salidas de esas funciones como entradas de la rutina primaria. En el float eval(expr)
"mundo real", reconocer y transformar las entradas es un problema fundamental. char expr [ J;
Supngase en este caso que cada lnea de entrada est. en forma de cadena de {
dgitos y smbolos de operadores; que los operandos son dgitos simples no negati- int e, position;
vos, por ejemplo, O, l, 2, ... ; 8, 9. As, una lnea de entrada podra contener 3 4 5 float opnd1, opnd2, value;
+ en las primeras cinco columnas seguidas de un carcter de fin-de-lnea(' /n'). Se float oper(), pop ();
quiere escribir unprograma que lea lneas de entrada en este. formato, hasta que no struct stack opndstk;
quede ninguna, e imprima para cada lnea, la cadena de entrada original y el resulta- opndstk.top = -1;
do de la expresin evaluada. for (position = D; (e expr[positiOnJ) != ,-,o, ;
Ya que los smbolos se leen como caracteres, debe encontrarse un mtodo para pos_ition++)
if (isdigit(c))
convertir los caracteres operandos a nmeros y los caracteres .operadores a opera- I* operando-- convertir el-carcter que
ciones. Por ejemplo, debe tenerse un mtodo para convertir el carcter '5' a nmero *I
I representa al dgitq en loat- y */
5 y el carcter ' + ' a la operacin de suma. I* se coloca en la pila *I
La conversin de un carcter a un entero puede manejarse fcilmente en C, si push(&opndstk, (float) (c-'0'));
int x es un carcter que representa un dgito en C, la expresin x- 'O' produce el valor else {
numrico de ese dgito. Para implantar la operacin correspondiente a un smbolo ! * operador *I
de operador, se usa una funcin oper que acepta la representacin con caracteres y opnd2 pop (&opndst~);
dos operandos como parmetros de entrada y da como resultado el valor de.la expre- opnd1 pop (&opndstk);
value oper(c, opnd1, opnd2);
sin obtenida mediante la aplicacin del operador a los dos operandos. El cuerpo de
push (&opndstk, value);
la funcin ser presentado en breve. I* fin de else *I
El cuerpo del programa principal sera el siguiente. La constante MAXCOLS return (pop(&opndstk));
es el nmero mximo de columnas en una lnea de entrada. I* fin deeval *!

92 Estructuras de datos en. C Pilas 93


1
1
como resultado de alguien que en forma inocente usa el programa para una expresin
Para completar se presentan las funciones isdigit y oper. La funcin isdigit ve- postfija que contiene nmeros de dos dgitos, lo que conduce a un nmero excesivo
rifica si su argumento es un dgito: de operandos. O quiz, el usuario del programa tiene la impresin de tratar con n-
meros negativos y estos tienen que introducirse con el signo menos, que es el mismo
isdigit(symb) que se usa para denotar la operacin de resta. Estos signos menos se tratan como
char symb; operadores de resta, lo cual da como resultado un nmero excesivo de operadores.
{ Dependiendo del tipo especfico de error, la computadora puede tomar una de varias
return(symb >- '0 1 && symb <- '9'); acciones (por ejemplo, detener la ejecucin o imprimir resultados equivocados).
Supngase que en la instruccin final del programa, la pila opndstk no est
vaca. No recibimos mensajes de error (porque no especificamos ninguno).y eval
. Esta funcin est disponible como marco predefinido en la mayora de lo~ ~istemas C. regresa un valor numrico para una expresin que quiz estuvo planteada de mane-
La funcin oper verifica si su primer argumento es un operadorvah~o, _en cu- ra incorrecta en principio. Supngase que una de las llamadas a la rutina pop suscita
yo caso, determina el resultado de su operaci? en los dos :~umentos sigmentes. la condicin de subdesborde. Como no se us la rutinapopandtest para eliminar ele-
Parn la exponenciacin suponemos la existencia de. una func1on expon(opl, op2). mentos de la pila, el programa se detiene. Esto parece poco razonable, dado que los
1
datos defectuosos en una lnea no deberan impedir el procesamiento de los datos de
floit oper(sym~, op1, op2) otras. De ninguna manera son stos los nicos problemas que podran surgir.
il'lt symb; Como ejercicio, pueden escribirse programas que acomodan entradas menes restric-
,float op_L_, op2_;_ tivas y otros que detecten algunos errores de los ya mencionados antes ..
floa t eX'on ( ) ;
{ Conversin de una expresin infija a postfija
switch (symb)
case '+ return ( op1 + op2);
case '- 1 return (op1 - op2).; Hasta aqu se han presentado rutinas para evaluar una expresin postfija.
case '* 1 return (op1 * op2); Aunque se ha discutido un mtodo para transformar la notacin infija a postfija, no
case / return ( op1 / op2); se ha presentado hasta a~ora un algoritmo para hacerlo. A ello se dirige ahora
case ' 1' : return ( expon ( op1, op2));
11
nuestra atencin. Una vez que se ha construido dicho algoritmo, se tendr la capaci-
default : printf("%s", "operacin ilegal ); dad de leer una expresin infija y evaluarla, primero convirtindola a postfija y
exit(1); luego evalundola como tal.
/* fin de switch *I En la discusin previa se mencion que las expresiones dentro de los parntesis
/* fin de/ oper *. I que son interiores deben convertirse primero a notacin postfija de tal manera que
puedan tratarse como operandos simples. De esta manera los parntesis pueden eli-
Limitaciories del programa minarse en forma sucesiva hasta que se convierta la expresion completa. El ltimo
par de parntesis por abrir dentro de un grupo ercierra la expresin que debe trans-
Antes de dejar el programa, deben sealarse algunas de sus deficiencias. En- formarse primero dentro de dicho grupo. Este comportamiento de ltimo en entrar,
tender aquello que;.; puede hacer un programa es t_an importante como sab~r lo que primero en salir debiera sugerir de manera inmediata el uso de una pila. .
puede hacer. Debiera ser obvio que intentar_resolve,r uny:oblema por medio de u_n Considrese las dos expresiones infijas A + B * C y (A + B) * C y sus ver-
programa produzca resuHados incorrectos sm la mas mmima huella de un mensaJe siones postfijas respectivas ABC * + y AB + C *.Encada caso, elorden de los ope-
tentar resolver un problema con un programa incorrecto, con lo cual se logra que ~l rndos es el mismo que en las expresiones infiJas orginales. Al examinar la primera
programa produzca resultdos incorrectr?s sin la msm?ima ~uella d_e u? m~nsaJe expresin, A + B * C, el primer operando, A puede insertarse de manera inmediata
que indique el error. En estos casos, el programador no llene nt~g~~a mdic_acin de dentro de la expresin postfija. De modo claro el smbolo + no puede insertarse
que los resultados son errneos y puede, por consiguiente, hacer JUICIOS eqmvocados hasta despus del segundo operando, que an no se ha examinado. Eri consecuencia,
basados en dichos resultados. Por este motivo, .es importante que el programador tiene que guardarse aparte para recuperarse e insertarse en su posicin adecuada.
entienda las linlitaciones del programa. . ,. . Cuando se examina el operando B, se inserta de manera inmediata despus de
Una crtica fundamental a este programa es que no hace nada en termmos de A. Ahora, sin embargo, se han examinado dos operandos. Qu impide recuperar e
detectar y actuar de acuerdo con los errores. Si los datos en ~ada lnea ,de entra~a insertar ( + )? La respuesta es, por supuesto, el operador * que sigue y tiene precel
representan una expresin postfija vlida, el programa funciona. Supongase, sm dencia sobre +. En el caso de la segunda expresin, el parntesis que cierra indtca
embargo, que una lnea de entrada tiene demasiados operadores u oper.andos o que que la operacin + debe realizarse primero. Recurdese que en notacin postfija, a
stos no se encuentran en la secuencia debida. Estos problemas podnan aparecer
Pilas 95
Estructuras de datos en C
94
diferencia de la in fija, el operador que aparece primero en la cadena es el que se apli- [El caso de op = = ')' se discutir en breve ] E
aparece despus de un par.ntes_is izquierdo se. ins:~~e :~~~r;il!~e un operador que
ca primero.
Dado que la prioridad desempea un papel tan importante en la transforma- Cuando se lee un parentesrs de cierre todos lo
rntesis de apertura debern extraerse de ia pila y ~o~per~dores, tasta el primer pa-
Esto puede hacerse al definir prcd(op, ')') como V~~~os;'/ a cadena postfija.
cin de infija a postfija, supngase la existencia de una funcin prcd(opl, op2),
donde op I y op2 son caracteres que representan operadores. Esta funcin regresa
VERDADERO si op I tiene precedencia sobre op2 cuando op I aparece a la izquier- operadores op distintos del parntesis izquierdo C d A ERO para todos los
eliminado de la pila y aparece el a . . uan o estos operadores se han
da de op2 en una expresin infija sin parntesis; prcd(opl, op2) regresa FALSO en i:
especial. El parntesis que abres~ ;e;;;:1s ~e apertura, deber realizarse una accin
otro caso. Por ejemplo, prcd(' + ', '+ ') son VERDADERO, mientras que
tesis que cierra descartarn en vez de coloec~;:e~::: l~ pila y en comp.~a del parn-
prcd(' + ', '*') es FALSO.
Se presenta ahora un esbozo de un algoritmo para convertir una cadena infija
sin parntesis a una cadena postfija. Como se supone que no habr parntesis en la
Establezcamos prcd('(', ')') como FALSO
1
rntesis que abre, se salta el ciclo que inici~ e:
apertura no se coloca en la cadena postfi' p 1O
~~~: :~ur:
1
n a cadena postfija o en la pila.
0
q~e0 al alcalnzar_se u~ pa-
' ~ q~; e parentesrs de
cadena de entrada, lo nico que rige el orden en el cual aparecen los operadores en la linea 9. Sin embar , 1 .or t~nto la e1ecuc1on contina en la
cadena posfija es la precedencia. (Los nmeros de lnea que aparecen en el algoritmo O
lnea 9 se remplaz; ;o~~:s:~s~:ec;i~arentesrs que cierra no se inserta en la pila, la
se usarn para futuras referencias.)
9 if (empty(opstk) 11 symb != ')')
1 opstk = la pila vaca
2 wbile (no es fin de entrada) push(opstk, symb);
3 symb = siguie_nte carcter en la entrada; else? ; * extraer el parntesis que abre y descartarlo *!
L; f (symb es un operando) topsymb = pop(opstk);
agrega symb a la cadena postfija
5 else 1 el alg;~ti;,;~spcuoe~~enciones anteriores p_ara la fu~cin prcd y la revisin de la lnea 9,
6 while(!empty(opstk) && prcd(stacktop(opstk),
symb)) usarse para convertrr cualqmer cadena inff . f.
meo a continuacin las reglas de precedencia para parntesis/ a post !Ja. Se resu-
7 topsymb = pop(opstk);
8 agrega topsymb a la cadena postfija:;
/* fin de while *I
prcd(' (, ,op) FALSE para cualqu~er operador op
9 push(opstk, symb); prcd(op,'(') FALSE para cualquier operador op que no sea ')'
prcd(op,, (,) TRUE para cualquier operador op que n'o sea ')'
I* fin de else *I
prcd('), ,op) undefined para cua quier operador op (un intento
I * fin de while */
I* extraccin d6 los _operadores restantes */ de comparar estos dos operadores indicar un
10 while_(!empty(opstk)) ( error)
ll topsymb = pop(opstk); Se ilustra este algoritmo con algunos ejemplos:
12 agregar topsymb a la cadena postfija
/* fin de whle */ Ejemplo l: A+ B *C
!
Simlese el algoritmo con caden_as infijas del tipo "A* B + C * D" y "A + B Los contenidos de svmb la cadena ostfi'a
* C $ D $ E" [donde '$' representa la exponenciacin y prcd ('$', '$') es FALSO] examinar cada smbolo i_ .' k P ' l. Y opS!k se muestran despus de
derecha. . a p, 1a op,t se muestra Junto con el valor del tope a la
para convencerse de su correccin. Ntese que en cada punto de la simulacin, un


1
operador en la pila tiene menos precedencia que tdos los operadores que estn por
encima de l. Esto se debe a que la pila vaca inicial satisface de manera trivial esta symb cadena postfija opstk
condicin y un operador se inserta dentro de la pila (lnea 9) slo si el operdor que
1 A A
est en el tope en ese momento tiene precedencia menor que el que va a entrar. 2 + A +
Qu modificacin deber realizarse a este algoritmo para utilizar parntesis? 3 B AB +
La respuesta es sorprendentemente corta. Cuando se lee un parntesis tiene que 4 AB +*
insertarse dentro de la pila. Esto puede hacerse al establecer la convenci.n de que 5 e* ABC +*
prcd(op, '(') es FALSO para cualquier operador op con excepcin del parntesis 6 ABC * +
derecho. Adems, se define prcd('(', op) como FALSO para cualquier operador op.
7 ABC *+

Estructuras de datos en C Pilas 97


96
1
! Las lneas l, 3 y 5 corresponden al examen de un operando; de ah que el smbolo P?r qu el algoritmo de conversin parece tan complicado, .mientras que el de
(symb) se coloca de manera inmediata en la cadena postfija. En la lnea 2 se examina evaluac10n parece tan sencillo? La respuesta es que el primero convierte al orden na-
un operador y se encuentra que la pila est vaca, en consecuencia se inserta el opera- tural (es decir, primero aparece la operacin que debe ejecutarse prime~o) a partir de
dor en la pila. En la lnea 4 la precedencia del nuevo smbolo(*) es mayor que la del un orden de precedencia (regido por.la funcin prcd y la presencia de parntesis).
smbolo en el tope de la pila (+);por lo que el smbolo nuevo se coloca en la pila. En Co~o son muchas las combinaciones de elementos en el tope de la pila (si no est
los pasos 6 y 7 la cadena de entrada est vaca, por lo tanto se eliminan los elementos v~cia) y los _eiememos que van a msertarse en la misma, se vuelve necesario un gran
de la pila para colocarse en la cadena postfija. num.ero de mstruc10nes con el fm de asegurar que se han cubierto todas las posibili-
dades. Por otra parte, en el otro algoritmo los operadores aparecen precisamente en
Ejemplo 2: (A+B)*C el orden en que deben ejecutarse. Por este. motivo los operandos pueden apilarse
hasta que se encuentre un operador, momento en el cual la operacin se ejecuta de
symb Cadena postfija opstk manera inmediata.
La motivacin que hay detrs del algoritmo de conversin es el deseo de sacar
( ( los operadores en el orden en el cual. deben ejecutarse. En la resolucin de ,este
A A (
(+ problema a mano, podran seguirse instrucciones vagas que exigiran convertir de
+ A
B AB (+ adentro hacia afuera. Esto funciona muy bien en el. caso de. seres .humanos que
) AB + resuelven un problema con papel y lpiz (si no se confunden,o cometen un error),
* AB + * Sm embargo un programa o algoritmo debe ser ms preciso en sus instrucciones. No
e AB +C * se puede estar seg_uro de haber alcanzado el parntesis ms interno o el operador con
AB + C* mayor precedencia hasta que se hayan explorado smbolos adicionales. En ese mo-
mento debe regresarse a algn punto previo. .
En este ejemplo, cuando se encuentra el parntesis derecho se extraen los ele- En lugar de explorar hacia atrs de manera continua se hace uso de la pila para
mentos de la pila hasta que se encuentra un parntesis izquierdo, en cuyo caso se des- "recor
d" ar Ios eI~mentos encontrados con anterioridad. Si se encuentra un opera-
cartan ambos. Al usar parntesis para forzar un orden de precedencia diferente al dor de precedencia mayor al que se encuentra en l tope de la pila, se le inserta
que se supone en su ausencia, el orden de aparicin de los operadores en la cadena el nuevo operador, lo cual significa que cuando todos los elementos finalmente se
postfija es diferente al del ejemplo l. extraen de la pila este nuevo operador preceder al que estaba en el tope dentro de la
cadena postfija (lo cual es correcto dado que tiene mayor precedencia). Si, por otra
Ejemplo 3: ((A - (B + C)) * D) $ E + F) parte, _la preceden~ia del. nuevo operador es menor, el operador del tope de la pila
debe eJeeutarse primero. En consecuencia, el tope de la,pila se saca y el smbolo que
symb cadena postfija opstk va a entrar se compara con el nuevo tope, y as sucesivamente. Los parntesis en la
( ( cadena de entrada modifican el orden de las operaciones. As; cuando se encuentra
( ( ( un parntesis izquierdo, se inserta en la pila. Cuando se encuentra el parntesis
A A ( ( derecho asociado a l, todos los operadores eQ\re ambos se colocan.en I.a cadena de
A ( (-
( (- (
salida, puesto que debern ejecutarse antes de que los que aparecen despus del
( A par.entes,s.
B AB ( ( - (
+ AB ((- ( +
e ABC (( ( + Programa para convertir una expresin de infija a postfija,
) ABC + ( (-
) ABC + - (
ABC + - Hay dos cosas que deben hacerse antes de que en realidad se inicie a escribir un
* (*
D ABC + -D (* programa. La primera es definir con precisin el formato de la entrada y de la sali-
) ABC + -D da. La segunda,. construir, o al menos definir aquellas rutinas de las que depende el
$ ABC + -D $ programa pnnc1pal. Suponemos que la entrada consiste en cadenas de caracteres
( ABC + -D * $(
una cadena por cada lnea de entrada. El final de cada cadena se seala mediante u~
E ABC + -D * E $(
ABC + -D * E + carcter de fin-de-linea ('/n'). Para simplificar suponemos que todos los operandos
+ $(
F ABC+-DEF $( + son letras o dgito: de un solo carcter. Todos los operador~s y parntesis se repre-
) ABC + -D * F + $ sentan por ellos mismos Y'$' representa la exponenciacin. La salida es una cadena
ABC+-DEF+ $ de caracteres. Estas convenciones hacen adecuada la salida del proceso de conver-

98 Estructuras de datos en C Pilas


99
sin para la evaluacin, con tal de que sean dgitos todos los operandos de un solo struct stack opstk;
carcter en la cadena inicial infija. opstk.top = -1; !* la pila vaca *,
Para transformar el algoritmo de conversin en un programa se usan varias ru-
tinas. Entre ellas se encuentran empty, pop, push y popandtest, todas modificadas for (position=D; (symb = infix[position]) != 1 \ Q1 ;

de manera conveniente para que los elementos de pila sean caracteres. Tambin se position++)
if (isoperand(symb))
usa una funcin isoperand que regresa VERDADERO si su argumento es un ope-
postr[outpos++J = symb;
rando y FALSO en caso contrario. Esta sencilla funcin la dejamos como ejercicio else {
al lector. popandtest (&opstk, &topsymb, &und);
De manera similar, la funcin prcdse deja como ejercicio al lector. Bsta acepta whlle (!und && prcd(topsymb, symb)) {
dos smbolos de operadores de un solo carcter como argumentos y regresa VER- postr[outpos++l - topsymb;
DADERO si el primero tiene precedencia sobre el segundo cuando aparece a su popandtest (&opstk, &topsymb, &und);
izquierda en una cadena in fija y FALSO en caso contrario. Por supuesto, la funcin / * fin de while * I '
incorpora las convenciones para los parntesis presentadas con anterioridad. if ( !und)
Una vez escritas estas funciones auxiliares, puede escribirse la funcin de con- push (&opstk, topsymb);
versin postfix y un programa que la llame. El programa lee una lnea que contiene .if (u,nd 11 (symb !."' ')'))
una expresin en notacin in fija, llama a la rutina postfix e imprime la cadena post- push(&opstk, symb)~
else
fija. El cuerpo de la rutina principal es la siguiente:
topsymb - pop (&opstk);
I* fin deeJs *I -
#define MAXCOLS 50, while (!empty(&opstk))
main () postr[outpos++l = pop(&opstk);
{ postr[outposJ = 1 \0';
char infix[MAXCOLS]; return;
char postr[MAXCOLS]; I * fin de postfix */
int pos= O;

while ((infix[pos++l - getchar()) !- '\n'); El programa tiene un defecto fundamental que consiste en no verificar s la ca-
infix[--posJ = '\0 1 ; dena de entrada es una expresin in fija vlida. En realidad, sera instructivo para el
printf("%s%s", "la expreSin infija original es ",infix) lector examinar la operacin de este programa cuando se presenta con una cadena
postfix(infix, postr); postfija vlida como entrada. Como ejercicio, proponemos escribir un programa
printf( 11 %s\n 11 , postr); que verifique s la cadena de entrada es una expresin in fija vlida.
/* fin main *I
Ahora puede escribirse un programa para leer una cadena in fija y computar su
valor numrico. S la cadena original consiste en operandos de un solo dgito sin que
La declaracin para la pila de operadores y la rutina postfix se presentan a con- sean letras, el siguiente programa lee la cadena original e imprime su valor.
tinuacin:
#define MAXCOLS BO
struct stack { main()
in t top; {
char items[MAXCOLS]; chaT instring[MAXCOLS], postring[MAXCOLS];
l; int position = O;
float eval ();
postfix(infix, postr)
char infix [ J; while((instring[position++J = getchar()) != '\h');
char postr [J; instring_(--posi tion) = 1 \O 1 ;
{ 'Printf("%s%s", "la expresin il1.ija es'\ instring);
int position, und; postfix(~nstting, pstring);
int outpos = o; print("%)f/n", "el valor es 11 , eval(posfring));
char topsymb = 1 +1 ; /* fin de main *I
char symb;

1.00 Estructuras de datos en C Pilas 101


Se requieren dos versiones diferentes de rutinas para manipular la pila (pop,
push, etc.) porque postfix usa una pila de operadores caracteres (es decir, opstk), 2.3.6. Escriba un. solo progr,ama combinandq las carac.t~rstk~s de eva/y postfix para e.~.-:
luar una cadena nfija. Use dos pilas, ria para ls operandos y otr.i para los operado-
mientras que eva/ usa una de operandos punto flotantes (es decir, opndstk). Por res, no debe convertirse primei::-o la cadena infija a postfija para luego evaluarse
supuesto, es posible usar una sola pila que pueda contener tanto reales como carac- como postfija, sino hacerse sobre la marcha.
teres al definir una unin tal y como se describi en la seccin l.3. 2.3.7. Escriba una rutina prefix para aceptar una cadena in fija y crear la forma prefija de la
Nuestra mayor atencin en esta parte se dirigi a las transformaciones que invo- misma, supngase que la cadena se lee de derecha a izquierda, de la misma forma en
lucran expresiones postfijas. Un algoritmo para convertir una expresin infija en que la cadena prefija se crea.
una postfija explora los caracteres de izquierda a derecha, apilndolos y desapiln- 2.3.8. Escriba un programa en lenguaje C para convertir
dolos cuando es necesario. Si se requiriera convertir de infija a.prefija, la cadena in- a. una cadena prefija a postfija
fija debera explorarse de derecha a izquierda e introducirse los smbolos apropiados b. una cadena postfija a prefija
a la cadena prefija de derecha a izquierda. Dado que la mayora de las expresiones c. una cadena prefija a infija
algebraicas se leen de izquierda a derecha, la notacin postfija es una eleccin ms d. una cadena postfija a infija
natural. 2,3.9. Escriba una rutina en C reduce que acepte una cadena in fija y forme una cadena infi-
Los programas anteriores nada ms indican el tipo de rutinas que podran ja equivalente de donde se hayan eliminado todos los parntesis innecesarios. Puede
escribirse para manipular y evaluar expresiones postfijas. De ninguna manera son hacerse sin usar una pila? l,

completos o nicos. Muchas de sus variantes tambin son aceptables. Algunos de los 2.3. IO. Supngase una mquina que tiene un solo registro y seis instrucciones.
primeros compiladores de lenguaje de alto nivel en real.idad usaban rutinas como LD A poner el operando A dentro del registro
eva/ y postfix para manejar expresiones algebraicas . Desde entonces se han ST A poner los contenidos del registro en la variable A
desarrollado tcnicas ms sofisticadas para tratar dichos problemas. AD A agrega los contenidos de la variable A al registro
SB A substrae los contenidos de la variable A del registro
ML A multiplica los contenidos del registro pr la variable A
EJERCICIOS
DV A divide los contenidos del registro por la variable A
Escriba un programa que acepte una expresin postfija compuesta de operandos de
2.3. t. Transforme cada una de las siguientes expresiones a prefija Y postfija.
u.na sola letra y de los,.operadores +, -,. *y/ _e .imprima_unasecuertcia de instnc-
a.A.+B-C CI<:Jnes para evaluar la expres.in: y dejar..'e~ result~do en el registro. Ufe variables de la
b. (A + .B)(C - D) $ E* F forilla .TEMP'? como variables temporales. Po.r ejemplo, al usar Ja expresin post.fa
c. (A + B)(C $(D - E) + F) - G . ,. ABC: * + DE - / deber imprimir lo siguiente: '
d, A + (((8 - C)(D - E) + F) / G)$(H - ])
2.3.2. Transforme cada. una de las siguientes expresiones prefijas a in fijas.
LD B
a. + - ABC
b. + A - BC
ML e
ST TEMP1
e, + + A -' * $ BCD/ + EF GHI
LD A
d. + - $ ABC * D ** EFG
AD TEMP1
2.3.3. Transforme cada una de las siguientes expre~iones postfijas a in fija.
a. AB + e -
b. ABC +- ST TEMP2
c. AB - C + DEF - + $ LD D
d. ABCDE + $ * EF * ~ SB E
2.3.4. Apliq~e el algoritmo de evaluacin presentado en el libro pafa valuar las siguientes ST TEMP3
expresiones postfijas. Supngase A = l, B = 2 Y C = 3. LD TEMP2
a. AB + e BA + e $ - DV TEMP3
b. ABC + * CBA - + * ST TEMPL,
2.3.5. Modifique la rutinft eval para que acepte como entr~da una ~aden de' caracteres de
operadores y operrildos ue representen una expresiri postfija y cree la forma in fija
con parentesis comrletos qe la expresin postfiJa original. Por ejemplo, AB + se
transformar en (.4 + B) y .48 + C - en ((.4 + B) '-- C).

102 Estructuras de datos en C Pilas


103
r,-~

3 cuencia se usa el signo de admiracin(!) pa a I f


puede escribirse su definicin como sigue: r
.. .
a unc1on factorial. En consecuencia,

n! = 1 if n == o
n! = n * <n - 1 ) * (
n - 2l * * 1 if n > o

Los .tres puntos en realidad son una abreviatura u . . . . ..


dos los nmeros entren _ 3 y 2 Para er . q e md,ca la mult1phcac1on de to-
Recursin 1
se tendra que elaborar una list~ de una ,~mar la abreviatura en la definicin de n !
siguiente manera: ormu a para cada n ! por separado de la

O! 1
1! 1
2! 2
3!
* 3
1

4!
* 2
* 1
4
... .... *
3 * 2 1
,
*

_Por supuesto, no puede esperarse la lista d . , .


cada entew. Para eliminar todas la.s ab . e u~a formula para el factorial de
finiciones, sin dejar de definir la f .r~v,adturas y evitar un conjunto infinito de de-
. uncwn e manera prec15 d
algontmo que acepte un entero n y re a, pue e presentarse un
grese e1 valor de n !
En este.captulo se presenta la reeursin, como una de las herramientas de progra-
macin ms poderosas y menos entendidas por los estudiantes que se inician en la dis- proa= :L;
ciplina. Asimismo, se define la recursin, se presenta s uso en C y se dan algunos for (X= n; X> O; X--)
ejemplos. Tambien se examina una implantacin de recursin mediante pilas. Por prod *= x
ltimo, se discuten las ventajas y desventajas del uso de la recursin en la solucin de return(prod);
problemas.
Tal algoritmo se llama iterativo or u . . .,
proceso hasta satisfacer cierta condc?, qEe reqmer~ la repet1c10n explcita de un
3.1. DEFINICION Y PROCESOS RECURSIVOS forma sencilla en una funcin de ~ ion. ste algoritmo puede transformarse en
de entrada. Un algoritmo puede concebq_ue regresa n ! ' cuando n es el parmetro
'-''d l" Irse como un programa , ,, .
En matemticas, muchos objetos se definen al presentar un proceso que los produce. ' .ea . que no tenga ninguna de las limitacione , . . para una maquma
Por ejemplo, se define como el cociente entre el permetro de una circunferencia y Y pueda,por lo tanto, usarse para definir un s/r~!1cas de un_a_computadora real
. ~ . ~nc1on matemat,ca'. Sin embargo,
su dimetro. Esto equivale al siguiente conjunto de instrucciones: obtener el
permetro de una circunferencia y su dimetro, dividir el primero entre el segundo y
una funcin en C no puede servir . a .
ria! debido a limitaciones como 1:
mquina real.
~:~f
!ef1mc1on ma_tem~tf ca d l funcin facto-
P s1 n y el tamano fm1to de los datos en una
llamar al resultado ,r. Es claro que el proceso especificado debe terminar con un re-
sultado determinado. Examinemos con ms detenimiento la d f . . ,
una frmula para cada valor den P d b e imc10n de n ! que da por separado
4*3*2 * l . . ue e o servarse por eJe l . .
La funcin factorial . , que es igual a 4 * 3!. En realidad .' mp o, que 4! es igual a
es igual a n * (n - !) ,' . Al m u11ip 1,car n por 'es d que,d para cualquier n > O, n'.
el obv10
Otro ejemplo de una definicin especificada mediante un proceso es la funcin n - y 1, se obtiene el producto de tod . l pro ucto e todos los enteros entre
factorial que desempea un papel importante en matemticas y estadstica. Dado un puede definirse: os os enteros desden hasta l. Por lo tanto,
entero positivo n, se define el factorial den como el producto de todos los enteros entre
n y l. Por ejemplo, el factorial de 5 es igual a 5 * 4 * 3 * 2 * 1 = l 20 y el factorial de 3 o! t
es igual a 3 * 2 * 1 = 6. El factorial de Ose define como 1. En matemticas, con fre-
:L ! 1 * O!
2! 2 * 1!

104 Recursin
105
3! = 3 * 2! 1 J.f (n == O)
L;!=./:;*3! 2, fact = 1;
3 else {
,; X= n - 1;
o, si se usa la notacin matemtica empleada antes; 5 f encpntrar el valor de xi. Llmelo y
6 fact = n ~ y;
7 I* fin de else *I
n! 1 if n == O
n! n * ( n - 1) ! if n > O.
Este algoritmo mu,estra el proceso usado para calcular n ! mediante la defini-
Esta definicin puede parecer muy extraa pues define la funcin factorial en cin recursiva. La llave para el algoritmo es, por supuesto, la lnea 5, ,donde se pide
sus propios trminos, lo cual da la impresin de ser circular e inaceptable hasta darse "encontrar el valor de x! ", que requiere volver a ejecutar el algoritmo con entrada
cuenta de que la notacin matemtica slo es una manera concisa de escribir el n- x, pues el m~todo para calcular la funcin factorial es el propio algoritmo. Para ver
mero infinito de ecuaciones necesarias para definir n! para cada n. O! se define en que el algontmo por fin se detiene, ntese que al principio de la lnea 5, x es igual a
forma directa como l. Una vez definido O!, la definicin de 1! como 1 * O! no es de n - 1. C?da vez que se ejecuta el algoritmo, la entrada disminuye en l con respecto
ninguna manera circular. Lo mismo ocurre cuando se define 2! como 2 1! una vez a la antenor, de tal manera que (como la entrada original n era un entero no negati-
definido l ! . Se puede argumentar que la ltima definicin es ms precisa que la defi- vo) Oser, a la larga, la entrada. En este punto, el algoritmo regresa simplemente J.
nicin den! como n * (n - 1) * ... * 1, paran >0, porque no requiere de los tres Este val?r es devuelto a la lnea 5 que pide la evaluacin de O!. La multiplicacin de y
puntos suspensivos para sustituirse segn (es lo que se espera} la intuicin lgica del (que es igual a J), por n(tambin igual a I} se ejecuta despus y se regresa el resulta-
lector. A tal definicin, que define un objeto en trminos de un caso ms simple de s do, Esta secuencia de multiplicaciones y regresos contina hasta que se evala el n !
mismo, se le llama definicin l'ecrsiva. original, En la siguiente seccin se ver mo convertir este algoritmo en un progra-
Veamos ahora cm puede usarse la definicin recursiva de la funcin facto-
rial para evaluar5!. La definicinestablece que 5! es igual a 5 * 4!. Entonces, antes
de evaluar 5! debe evaluarse 4!. Al usar una vez ms la definicin, se ve que 4! = 4 *
.:r
ma en lenguaje C
supuesto, es ms simple y directo usar el mtodo iterativo para evaluar la
func1on faetona!. Se expuso como ejemplo simple para presentar la recursin no
3!. Por lo tanto, debe evaluarse 3!. Al repetir el proceso, se tiene que como el mtodo ms efectivo para resolver este problema en particular. De he~ho,
todos los problemas en esta seccin pueden resolverse de forma ms eficiente
1 S! 5 * ,; ! media~e la_ it.eracin. Sin embargo, ms adelant.e,en ste y otroscaptulos, se en-
,; ! ,; 3!
2 * 3! 3 2!
contraran eiemplos que se resuelven con mayor facilidad mediante mtodos recursi-
3 * 2! 2 * li!
vos.
,;
1!' 1 * O!
5 Multiplicacin de nmeros naturales
O! = 1
6

Cada caso se reduce a uno ms simple hasta que se llega a O!, el cua\se define Otro ejemplo de definicin recursiva es la multiplicacin de nmeros natura-
de manera directa como l. En la lnea 6 se tiene un valor definido .en forma directa les,,El producto a * b, donde a y b son enteros positivos, puede definirse como a
no como el factorial de otro nmero. Por lo tanto, puede regresarse de la lnea 6 a.la sumada as misma b veces. Esta es una definicin iterativa. La definicin .recmsiva
lnea J, llevando. el valor calculado en una lnea para,evaluar el resultado ,dela lnea equivalente es la siguiente:
anterior. Esto produce:
a * b a if b == 1

61 o! 1
a * b a * ( b - 1) + a if b > 1
O! 1 1, 1
5 1!
1 1 * * Para evaluar 6 * 3 mediante esta definicin, primero debe evaluarse 6 2 y
1! 2 1 2
,; 12! 2 * * 6 sumarle* despu_es 6. Para evaluar 6 * 2, se evala primero 6 * y luego se le suma 6.
3 2
31 3! 3
,;
* 2!
,;
* 6, 2,; Pero 6 l es igual a 6 segn la primera parte de la definicin. Entonces
2 1 ,; !
* 3!
!' 5
* 2~ = 120
1' 5 ! 5 * L;
* 6 * 3 = 6 ~ 2 + 6 = 6 * 1 + 6 + 6 = 6 + 6 + 6 = 18
Intentemos incorporar este proceso a un algoritmo. Una vez ms se quiere que
Se pide al lector como un ejercicio sencillo, convertir la definicin anterior en un al-
el algoritmo tenga como entrada un nmero entero n no negativo y calcule en una
goritmo recursivo.
variable fact el entero no negativo del factorial den.

Estructuras de datos en C Recursin. 107


106
3 + 1 + fib(O) + fib(1) + fib(s) -
Ntese la pauta que existe e1 una definicin recursiva. Se prese?ta un caso ,; + O+ 1 + fib(2) + fib(3) S + fib(O) + fib(1) + fib(3)
simple del trmino por definir, de manera explcita (en el caso del faetona!, O!_ se de- S + O + 1 + fib(1) + fib(2) - 6 + 1 + fib(O) + fib(1) -
fini como 1; en el de la multiplicacin a * 1 como a). Los otros c~sos se defm~n ) 7 + O + 1 8
aplicar alguna operacin al resultado de la evaluacin de un caso mas simple. _Asi,_n_.
se define en trminos de (n - ])! y a *ben trminos de a* (b-:- 1). Las simphfi- Obsrvese que la definicin recursiva de los nmeros de Fibonacci difiere de
caciones sucesivas de algn caso en particular tienen que _c_onducir a la larga al c~? las definiciones recursivas de la funcin factorial y de la multiplicacin. La defini-
trivial definido de manera explcita. En el caso de la funcion factonal l~ sustr~;cion cin recurs.iva defib se refiere dos veces a s misma. Por ejemplo,fib(6) = fib(4) +
sucesiva de 1 al valor n conduce, finalmente, a O. En el caso de la multipl'.cacio~, la Jib(5), de tal manera que al calcular fib(6),fib tiene que aplicarse de manera recursi-
sustraccin sucesiva de al valor b conduce, finalmente, a l. Si no sucediera asi, la va dos veces. Sin embargo, calcular Jib(5) tambin implica calcular fib(4), as que al
definicin no sera vlida. Por ejemplo, si definimos: aplicar la definicin hay mucha redundancia de clculo. En el ejemplo anterior,
Jb(3) se calcula tres veces por separado. Sera mucho ms eficiente "recordar" el
n! - < + 1)1/(n + 1) valor defib(3) la primera vez que se calcula y volver a usarlo cada vez que se necesi-
te. Es mucho ms eficiente un mtodo iterativo como el que sigue para calcular
o fb(n):
a * b - a * (b. + 1) - a
if ( n <- 1)
sera imposible determinar el valor de 5! o 6 * 3. (S~ invita al lector que intente deter- return(n);
minar esos valores mediante las definiciones antenores.) Esto sucede a pesar d~ ~ue lofib = O;
. hifib - 1;
las dos definiciones son vlidas. Sumar de manera c.ontinua 1. a.~ o b no produc'.r~ al
for ( i = 2; i <= n; i++) {
final un caso definido de manera explcita. An si 100! se defmio de forma expbc1ta,
X = lofib
cmo podra determinarse el valor de 101!? lofib - hifib;
hifib =X+ lofib;
La secuencia de Fibonacci I* fin de for' *I
return( hifib);
Examinemos un ejemplo menos familiar. La secuencia de Fibonacci, que es la
Comprese el nmero de adiciones (sin incluir los incrementos de la variable
secuencia de enteros
ndice,i) que se ejecutan para calcular Jib(6) mediante este algoritmo al usar la defi-
o, l, l, 2, 3, 5, 8, 13, 21, 34, ... nicin recursiva. En el caso de la funcin factorial, tienen que ejecutarse el mismo
nniero de multiplicaciones para calcular n ! mediante ambos mtodos: recursivo e
Cada elemento en esta secuencia. es la suma de los dos precedentes. (por ejempl~ iterativ. Lo mismo ocurre con el nmero de sumas en los dos mtodos al calcular la
o+ 1, + r = 2, + 2 = 3,2 + 3 = 5, ... )._Seanfib(O) =_0,ftbO) = 1 ya~1 multiplicacin. Sin embargo, en el caso de los nmeros de Fibonacci, el mtodo re-
sucesivamente, entonces puede definirse la secuencia de Fibonacci mediante la defi- cursivo es mucho ms costoso que el iterativo. En una seccin posterior se hablar
ms acerca de los mritos relativos de ambos mtodos.
nicin recursiva:

fib(n) - n if n -- O or n -- 1 La bsqueda binaria


fib(n) - fib(n - 2) + fib(n - 1) if n >- 2
Pudo haberse obtenido la falsa impresin de que la recursin es una. herra-
Por ejemplo, para calcular Jib(6), puede aplicarse la definicin de manera recursiva mienta que est muy a la mano para definir funciones matemticas pero que no. tiene
para obtener: ninguna influencia en actividades de clculo ms prcticas. El siguiente ejemplo
ilustra una aplicacin de la recursin en una de las actividades ms comunes en com-
fib(6) - fib(s) + fib(S) - fib(2) + fib(3) + fib(S) - putacin: la bsqueda.
fib(O) + fib(1) + fib(3) + fib(S) O + 1 + fib(3) + fib(S)
Considrese un arreglo de elementos en el cual se han colocado los objetos en
1 + fib(1) + fib(2) + fib(S) - algn orden. Por ejemplo, puede concebirse un diccionario o una libreta de telfo-
1 + 1 + fib(O) + fib(1) + fib(S) -
2 +o+ 1 + fib(S) - 3 + fib(3) + fib(s) nos como un arreglo cuyas entradas estn en orden alfabtico, El archivo con la lista
3 + fib(1) + fib(2) + fib(s) - de pago puede estar ordenado de acuerdo con los nmeros del seguro social de sus

Estructuras de datos en C Recursn 109


108
1
!
empleados. Supngase que tal arreglo existe y que se desea encontrar uno de sus ele- 7 buscar x entre alow y amid -1
mentos en particular. Por ejemplo, se quiere buscar un nombre en una libreta de 8 else
telfonos, una palabra en un diccionario o un empleado en un archivo de personal. g buscar x entre amid +. l y amid -1

El proceso usado para encontrar una entrada se llama bsqueda.


Como la bsqueda es una actividad tan comn en computacin, es conveniente Como se incluye la posibilidad de una bsqueda intil (es decir, puede no exis-
encontrar un mtodo eficiente para ejecutarla. Quizs el mtodo de bsqueda menos tir el elemento en el arreglo), se ha cambiado un poco el caso trivial. La bsqueda de
refinado es el de bsqueda lineal o secuencial, en el cual se examina cada uno de los un elemento en un arreglo no se define en forma directa como el ndice apropiado.
elementos del arreglo y se le compara, cada vez, con el que se busca hasta que ocurre Ms bien se compara el elemento con el que se est buscando. Si no son iguales, la
una coincidencia. Si la lista est desordenada y construida al azar, la bsqueda lineal bsqueda contina en la ''primera" o "segunda" mitad (ninguna de.las cuales con-
puede ser la nica va de encontrar algo en ella (a no ser, por supuesto, que se ordene tiene elementos). Este caso se indica por la condicin low > high y su resultado se
primero). Sin embargo, no debera usarse ste para buscar un nombre en un directo- define de manera directa como -1. ,
rio telefnico. En su lugar, se abre el libro en una pgina cualquiera y se examinan Apliquemos el algoritmo a un ejemplo. Supngase que el arreglo a contiene los
los nombres que aparecen en ella. Como estn ordenados en forma alfabtica, dieho elementos: 1, 3, 4, 5, 17, 18, 31, 33, en ese orden y se quiere buscar 17 (es decir, x es
examen determinar si debe continuarse la bsqueda en la primera o segunda mitad. igual a 17) entre el elemento O y el 7 (es decir, low es Oy high es 7). Al aplicar el algo-
del libro. ritmo se obtiene:
Apliquemos esta idea a la bsqueda en un arreglo. Si el arreglo slo contiene
un elemento, el problema es trivial. En otro caso, comprese el elemento que se bus- lnea 1: Es low > high? No lo es, entonces ejectese la lnea 3.
ca con el elemento central del arreglo. Si son iguales, se ha concluido con xito la lnea 3: mid (O + 7)/2 = 3.
bsqueda. Si el elemento central s mayor, se repite el proceso de bsqueda en la pri- lnea 4: Es x = = a[3]? 17 no es igual a 5, entonces ejectese la lnea 6.
mera mitad del arreglo (dado que si el elemento buscado aparece en algn lugar debe lnea 6: Es x< a[3]? 17 no es menor que 5, entonces ejectese la clusula else
hacerlo en la primera mitad): en caso contrario, se repite el proceso con la segunda de la lnea 8.
mitad. Obsrvese que cada vez que se realiza una comparacin, se divide en dos el lnea 9: Reptase el algoritmo con low mid + 1 = 4 y high = high = 7; i.e.
nmero de elementos que an deben buscarse. Para arreglos grandes ste mtodo es bsquese en la mitad superior del arreglo.
superior al de bsqueda secuencial en el cual cada compara<:in redu.c:e slo en 1 el lnea 1: Es 4 > 7? No, entonces ejectese la lnea 3,
nmero de elementos que an deben examinarse. Este mtodo. se llarna busqueda lnea 3: mid ; (4 + 7)/2 = 5.
binaria, debido a que el arreglo donde se realiza la bsqueda se divide en dos partes lnea 4: fx .. a[5]? .17 no es igual a 18, ento.nces ejectese la Unea 6.
iguales. lnea 6: Es x > a[5)? S, puesto que 17 < 18, entonces bsquese x entre a[lo~]
Obsrvese que se defini la bsqueda binaria, de manera .bastante natural, en y a[mid-1].
forma recursiva. Si el elemento que se busca no es igual al central del arreglo, 1s ins~ lnea 7: ~eptase el algoritmo con low = low = 4 y high mid -1 = 4. Se
trucciones indican buscar en un subarreglo mediante el mismo mtodo.As, el mto- aisla x entre el elemento cuarto y elemento cuarto de a.
do de bsqueda se define en trminos de s mismo con un arreglo ms pequeo como lnea 1: Es 4 > 4? No, entonces ~jectese la lnea 3.
entrada. Se puede estar seguro de que terminar el proceso porque los arreglos de lnea 3: mid = (4 + 4)/2 = 4.
entrada se hacen cada vez ms pequeos y se define de manera no recursiva la bs- lnea 4: Como a[4] = 17, regresa mid = 4 como respuesta. 17 es en realidad
queda en un arreglo de un elemento, dado que el elemento intermedio de tal arreglo el cuarto elemento del arreglo.
es su nico elemento.
Presentamos ahora un algoritmo recursivo para buscar un elemento x entre Obsrvese la pauta de llamadas y regresos del algoritmo. Un dl~grama que
a[low] y a[high] en un arreglo ordenado a. El algoritmo regresa un ndice de a, tal representa dicha pauta aparece en la figura 3.1. L Las flechas continuas indican el
que a[index] ; x si existe index entre low y high. Si no se encuentra.X en esa parte flujo de control a lo largo del algoritmo y las llamad.as recursivas. Los regresos se in-
del arreglo binsrch regresa -1 (en C no puede existir un elemento a[-1]). dican c_on lnea punteada. Como no hay pasos por ejecutarse en el algoritmo despus
de la linea 7 u 8, el resulttdo se regresa intacto a la ejecucin previa. Por ltimo,
cuando el control regresa a'la ejecucin original, la respuesta regresa al punto de !la-.
1 i f ( low > high) mada.
2 return(-1);
3 mid - ( low + high) I 2; , Examinemos cmo busca el algoritmo un elemento que no est en el arreglo.
,; i f (X -- a[mid]) Supongase que el arreglo a es el mismo que en el ejemplo previo donde se busca x
5 return ( mid); igual a 2.
6 i f (X < a[midl)

110 Estructuras de datos en C Recursin


111
!.
cuenda infinita de llamadas a s mismo. Claro que cualquier algoritmo que genere
1 lnea: Es low > high? O no es mayor que 7, entonces ejectese la lnea 3.
i tal secuencia no termina nunca. Una funcin recursiva/ debe definirse en trminos
lnea 3: mid = (0 + 7)/2 = 3. que no impliquen a/ al menos en un argumento o grupo de argumentos. Debe existir
lnea 4: Es x = = a[3]? 2 no es igual a 5, entonces ejectese la lnea 6.
una "salida" de la secuencia de llamadas recursivas. En los ejemplos de esta seccin,
lnea 6: Es x < a[3]? S, 2 < 5, entonces bsquese x entre a[/ow] y
los segmentos no recursivos de las definiciones fueron:
a[mid-1].
lnea 7: Reptase el algoritmo con /ow = /ow = O y high = mid -1 = 2. Si
aparece 2 en el arreglo tiene que estar entre a[O] y a[2], inclusive. factorial : O! = 1
lnea 1: Es O > 2? No, ejectese la lnea 3. multiplicacin : a * 1 a
lnea 3: mid = (O + 2)/2 = l. secuencia de Fibonacci . : fib(O) = O; fib(1) 1
lnea 4: Es 2 = = a[!]? No, ejectese la lnea 6. bsqueda binaria : i f (low > high)
lnea 6: Es 2 < a[l]? S, dado que 2 < 3. Bsquese x entre a[/ow] Y return(-1);
i f (x == a[midl)
a[mid-1]. return.( mid);
lnea 7: Reptase el algoritmo con /ow low = O y high = mid -1 = O. Si
. x existe en a tiene que ser el primer elemento. Sin esta salida no recursiva, no puede calcularse ninguna funcin recursiva. Cual-
lnea 1: Es O > O? No, ejectese la lnea 3. quier caso de definicin recursiva o invocacin de un algoritmo recursivo tiene que
lnea 3: mid = (O + 0)/2 = O. reducirse a la larga a alguna manipulacin de uno o casos ms simples no recursivos.
lnea 4: Es 2 = = a[O]? No, ejectese la lnea 6.
lnea 6: Es 2 < a[O]? 2 no es menor que 1, entonces ejectese la clusula else
en la lnea 8.
lnea 9: Reptase el algoritmo con /ow = mid + 1 = 1 y high = high = O.
lnea 1: Es /ow > high? 2 es mayor que 1, entonces el r:esultado es -l. El ele-
mento 2 no existe en el arreglo. j_1.1. Escriba un alg.oritITlo iterativo .Para evaluar a *-b mediante la suma, donde a y b son en-
. _ teros _no negativos.
Propiedades de las definiciones o algoritmos recursivos 'i 't'.2. E~criba una definicir-recursi~a de i/ ~ b, donde a y b son enteros no negativos, en tr-
Resumamos lo que implica una definicin o algoritmo recursivo. Un requisito mmos de la funcin sucesor, succ, definida como:
importante para que sea correcto un algoritmo recursivo es que no genere una se-
SUCC(X)
int x;
En"- {
Lnea 1 return ( x++);
Linea 3 I* fin de suco *I
Lnea 6

Lnea 9 L--------..
3.1.3. Sea..aun arreglo de enteros. Presente algoritfilos recursivos para calcular
a. el elemento mximo del arreglo
' b. el elemento mnimo del arreglo
Afuera .......-----+ Lnea 1 C, la suma de los elementos del lrreglo
\ Lnea 3 d. el_ producto de los elementos del arreglo
1 Lnea 4 e. el promedio.de los elementos del arreglo
1 Lnea 6
3.1.4. Eval~ mediante ambas definiciones, recursiva e iterativa, cada una de las siguientes
L-- _---- L,.,n,,e~a_:_7 -!---------.
0
expresiones.
Respuesta 1 Lnea 1
a. 6!
\ Lnea 3 b. 9!
1
l Lnea 4 c. 100 * 3
1
1
d. 6 * 4
1 e. Jib(IO)
i...,.. __ ..::,., ___ ..:. _____ (Respuesta encontrada)
f. Jib(l l)
Respuesta
Figura 3.1.1 Representc1cin en diagrama del algoritmo de bsqueda binaria.

Estructuras de datos en C
113
112
En la instruccin y = fact(x); la funcin jact se llama a s misma. Este es el
3.1. S. Sup
onga que un arreglo de diez enteros contiene los elementos
ingrediente esencial de una rutina recursiva. El programador supone que la funcin
1. 3, 7, 15, 21, 22, 36, 78, 95, 106 que se calcula ya fue definida y la usa dentro de su propia definicin. Sin embargo,
tiene que asegurarse de que no conduzca a una serie interminable de llamadas.
Use la bsqueda recursiva binaria t?ara encontrar cada uno de los siguientes elem~ritos Examinemos la ejecucin de esta funcin cuando la llama otro programa. Por
en el arreglo ejemplo, supngase que el programa de llamada contiene la siguiente instruccin
a. 1
b. 20 'printf( 11 %d 11 , fact(~));
c. 36 . ~f
3.1.6. Escriba una versin iterativa del algorifrh? de bsqueda binana. (Sugerencra:mO 11- Cuando la rutina de llamada invoca ajact, el parmetro n se iguala a 4. Como n no
que los valores de ow y high en forma directa.) . es O, x se iguala a 3. En este punto, se llama ajact por segunda vez con un argumento
, 1. . La funcin de Ackerman se define de manera recursiva para enteros no negativos,
3 7 de 3. Por lo tanto la .funcin jact se acta de nuevo y se vuelven a asignar las va-
como sigue: riables locales (x y y) y el parmetro del bloque (n ). Como la ejecucin todava no
si m == O deja la primera llamada ajact, se conse,va la primera asignacin de esas variables.
a(m, n)=n + l Asi, existen dos generaciones simultneas de esas variables. En cualquier punto de la
a(m,n) = a(m - l, l) si m _! = O, n == O segunda ejecucin de facf, slo se puede hacer referencia a las copias ms recientes
a(m,n) = a(m - l, a(m,n ~ 1)) sfm!=O~n!=O de estas variables.
En general, cada vez que la funcin fact se ingresa de manera recursiva, se
a. Mediante ta definicin 3.nterior, muestre que a(2, 2) es igual a 7, . asigna_ uo'nuevo conjunto de variables y parmetros y slo se puedhacer referencia
b. Pruebe que a(m, n) est definida para todos los enteros no negativos m Y n, a este conjunto dentro de dicha llamada ajact. Cuando se regresa defact a un punto
c. Se puede encontrar un mtodo iterativo para calcular a(m, n)? en ur\a llamada previa, se libera la asignaci6n ms reciente de dichas variables y se
J.1.8. Cuente el nmero d_e adido!1es necesa.~ias rar~
calcular fib(n) para O < : n ~ = 10 me- reactiva la copia previa, la cual es aqueUa asignada durnnte la entrada original a la
llamada previa y es propia de esa llamada.
diate'affibos rntodos:itratvo y recursivo. Aparece alguna pauta. _ .
3.1. 9. Si un arreglo contiene n elementos, cul es _e_l nperci m~xii:11 de llamada_~--- recuv_1vas Esta descripcin sugiere el uso de una pila para almacenar las generaciones
hechas'Pbr el alg'oritr1o de bsqueda binaria? sucesivas de parmetros y variables locales, la cual conserva el sistema C de manera
invisible para el usuario. Cada vez que se'ingresa una funcin recursiva, se inserta en
el tope de la pila una nueva asignacin de sus variables. Cualquierreferencia a una
RECURS10N EN C
vari_able local o parmetro se da a travs del tope actual de la pila. Cuando se regresa
3.2. la funcin, se libera la localidad del tope Y la localidad previa se convierie en el tope
actual que debe usarse para hacer referencia a las variables locales, Este mecanismo
Factorial e_n C
se examina con ms detalle en la seccin ( pero por ahora, veamo.s su aplicacin en
El lenguaje c permite al programador defin: subru.tinas Y fun~iones que sella- el clculo de la funcin factorial.
man a s mismas a las. c_uale_s se les llama recursivas. . : La figura 3.2.1 contiene una serie de imgenes de la pila para las variables n, x
El algoritm~ recursivo para calcular n ! puede transformars~ en forma directa y y durante la ejecucin de la funcin jact. Al inicio, las pilas estn vacas, como se
en una funcin C de la siguiente manera: ilustra en la figura 3.2. la. Despus de la-primera llamada ajact por el procedimiento
de llamada, la situacin es la que se muestra en la figura 3.2.1 b, con n igual a 4. Las
variables x y, y estn asignadas pero no inicializadas. Como n no es igual a O, x se
fact(n) iguala a 3 y se llama i.fact(3) (figura 3.2, le), El nuevo valor den no es O, por lo tan-
int n;
to se iguala a 2 y se llama afact(2) (figura 3.2.ld).
{
int x, y; Esto contina hasta que n es igual a O (figura 3.2.1 f). En ese momento, se
regresa el valor 1 de la llamada afact(O). La ejecucin se reanuda a partir del punto
if ( n == O) ene/ cul fue llamada jact(O), que es la asignacin del valor que se regres a la copia
return(1); dey declarada enfact(I). Esto se ilustra mediante el estado de la pila que se muestra
X =- n-1, en la figura 3.2. lg donde se ha liberado las variables asignadas para fact(O) y se
y= fact(x); iguala y a l.
return(n * y);
I* fin de fact *I

Estructuras de datos en C 115


114
Transformemos algunos de los otros procesos y definiciones recursivos de la
seccin anterior en programas recursivos en C. Es dificil concebir que un programa-
dor de C escriba un programa para calcular el producto de dos enteros positivos en
trminos de la adicin, dado que un asterisco lo hace de manera directa. No obstante,
2 una funcin que lo haga puede servir como ejemplo para ilustrar la recursin en C.
3 3 2
4 4 3 4 3 Al seguir la definicin de multiplicacin de la seccin previa, puede escribirse:
n X y n X y n X y n X y
mult(a, b)
(a) (Al inicio). (b) fact (4). (e) fact (3). (d) fact (2).
int a, b;
{
return(b = 1? a mult(a, b-1) + a);
I* fin de mult *I

1
2
3

r
2 1
.


.

.
o
2
3
1

o
1
2




1
2
3
o
1
2


2
3 2
1

Obsrvese cun similares son este programa y la definicin recursiva de la ltima
seccin. Dejamos como ejercicio, al lector continuar con la ejecucin de esta fun-
4 3 4 3 4 1 3 4 3 cin cuando se llama con i:Ios enteros positivos; el uso-de pilas es de gran ayuda en
n X y n X y n X y n X y ee proceso de rastreo.
Este ejemplo ilustra cmo puede llamarse a s misma una funcin recursiva,
(e) fact (1). (t) fact (0). (g) y = fact (0). (h) y = fact (1). a.un dentro de una instruccin donde se le asignen valores. De manera similar,
podra haberse escrito la funcin reutsiva fact en forma ms breve como sigue:

fact(n)
int n;
{
3 2 2 return(n ==O? 1 n fact(n-1));
4 3 4 3 6 I* fin defact *I

n X y n X y n X y
Esta versin reducida evita el uso explcito de variables locales x (para guardar
(i) y = fact (2) U) y "' fact (3). (k) printf (%d, fact (4)). el valor den -1) y y (para guardar el valor dejact(x)). Sin embargo, de todos mo-
dos se apartan localidades temporales para esos dos valores en cada llamada a la
Figura 3:2.1 La pila en diferentes momentos de !a ejecucin. (El asterisco indica
un valr no inicializado.}
funcin. Esas localidades se tratan de manera similar a como se trata cualquier va-
riable local explcita. As, al trazar la accin de una rutina recursiva, puede ser de
utilidad declarar todas las variables temporales en forma explcita. Vase si es de al-
Despus se ejecuta la instruccin return(n y), al multiplicar los valores del to- guna manera ms fcil seguir la accin de la siguiente versin ms explcita de mult:
pe den yy para obtener 1 y regresar este valor ajac/(2) (figura 3.2. lh). Este proceso
se repite dos veces ms, hasta. que, por ltimo, el valor de y en fact(4) es igual a 6 mult(a, b)
(figura 3.1.Jj). La instruccin return(n y) se ejecuta una.vez ms. El producto 24 int a, b;
se regresa al procedimiento de llamada donde se imprime mediante la instruccin {
int e, a, sum;
printf( 11 %d 11 , fact(~)); if (b =- 1)
return(a);
Obsrvese que cada vez que se regresa una rutina recursiva, lo hace al punto e= b-1;
que sigue de manera inmediata a aqul desde el cual se le llam. As, la llamada re- d = mult(a, e);
sum = d+a.;
cursiva a fact(3) regresa a la asignacin del. resultado a y dentro de fact(4), pero la rturn ( sum);
llamada recursiva afact(4) regresa a la instruccin printf en el programa de llamada. !* fin demult *!

116 Estructuras de datos en C Recursin 117


Otro punto que debe analizarse es que tiene particular importancia verificar la fib(n)
validez de los parmetros de entrada en una rutina recursiva. Por ejemplo, examine- int n;
mos la ejecucin de la funcin fact cuando se llama por medio de una instruccin
int X, y;
como la siguiente:
1f(n<=1)
printf( 11 \n%d 11 , fact(~1));
return(n);
X= fib(n-1);
Por supuesto, no se dise la funcin fact para producir un resultado significativo y = fib ( n-2);
para una entrada negativa. Sin embargo, una de los asuntos ms imponantes para return(x + y);
un programador es el de aprender que una funcin se encontrar invariablemente !* fin de fib *f
con una entrada nula y el error.resulta.nte puede ser muy difcil de encontrar a menos
que se hayan tomado en cuenta las posibles entradas errneas.
Por ejemplo, cuando se transfiere -1 como parmetro afact, de tal manera . Sigamos la accin de esta funcin cuando se 'calcula el sexto nmero de Fibo-
que n -1, x se iguala a ---2, y el cu.al se transfiere para una llamada recursiva a nacci. Puede compararse dic~a accin con el clculo manuaf que se ejecut en' la
fact. Se asigna otro grupo de valores a n, x y y, n se. hace igual a -2 y x se convierte ltima seccin para calcular fib(6). El proceso de apilamiento se ilustra en la figura
en -3 .. Este proceso contina hasta que al programa. se queda sin tiempo o espacio, 3.!_:?'. Cuando se llarna el prngrama la primera vez, se asigna memoria para lasva-
o bien el valor de x se hace muy pequeo. No se enva ningn mensaje indk,mdo la riables n, x y y, y n se iguala en forma recursiva a 6 (figuraT2.2a). Cno n > 1, se
evalan n - l y se llama en forma recursiva afib. Se asigria un nuevo conjnto a n
verdadera causa del error.
Si se llam afact al principio con una expr,esin complicada como argumento y x yy, y s~ igualan a 5 (figura 3.2.2b). El proceso contina (figuras 3. 2.2c-f) con ca'.
la expresin se evalu por error como nmero negativo, un programador podra pa-. da valor sucesivo de n disminuido ~n uno respecto a su antecesor, hasta que se llama
sar horas buscando la causa del error. El problema se puede remediar revisando la afib con n igual a 1. La sexta llamada afib regresa I a quien la llarn, de tal manera
funcin fact para verificar sus entradas de manera explcita, como sigue: q11e en la quinta asignacin de memoria, x toma el valor de 1 (figura 3.2.2g).
La siguiente nstruccin secuencial, y = fib(n - 2) se ejecuta despus. El va-
lor den que se usa es el que se asign ms recientemente, 2. As, llamamos de nuevo
fact(n) afib c?11 argumento O (figura 3.2.2h). De inmediato se regresa el valorde O, de tal
int n; manera que y se iguala a O en/ib(2) (figura 3:3.2i). Obsrvese que cada llamada re-
{ cursiva Hene como resultado un regreso al punto de llamada de manera que laUamada
int X, y;
a /ib(l) regresa la asignacin x y la llamada a/ib(O) a la asignacin y. La siguiente
if ( n < o) instruccin que debe ejecutarse en /ib(2) es la que regresa x + y = l + O = a
printf( 11 %s 11 ,
la }nstruccin que llama afib(2) en la generacin de la funcin que calcula/ib(3).
'\parmetro ne.gativo en la funcin .factorial" ) ; Esta instruccin es la de asignacin de x, por lo que se le da a x en /ib(3) el valor
exit(.1); /i~(2) = 1 (figurn 3.2.2j). El proceso de llamada, insercin, re~reso y eliminacin
} /* fin.de if *f contina hasta que la rutina regresa por ltima vez al programa principal con el
i f ( n == o) valor 8. La figura 3.2.2 muestra la pila hasta el momento en que /ib(5) llama a
return(1); /ib(3), de tal manera que su valor pueda asignarse ay. S pide aflector completr la
X= n-1; ilustracin con los estados de la pila para el resto de la ejecucin del programa.
y = fact(x); Est~ programa ilustr~ cmo p~ede Hanarse a s misnavarias veces con argu-
return(n * y);
mentos diferentes una rutina recursiva. De hecho, siempre y cuando slo. use va-
I* fin defact *I
nables locales una rutina recursiva, el programador puede usarla tal y como usa
cualquier otra Y suponer que ejecuta su funcin y produce el valor deseado. El
De manera similar, debe cuidar que un valor no positivo se encuentre el segundo pa- programador no necesita preocuparse por el mecanismo subyacente de la pila.
rmetro de la funcin mult.
Los nmeros de Fibonacci en C La bsqueda binaria en C
Prestemos de nuevo atencin a la secuencia de Fibonacci. Un programa en C
para calcular el n-simo nmero de Fibonacci puede extraerse casi sin cambio de la Presentemos ahora un. programa e~ C para realizar la bsqueda binaria. La
definicin recursiva: funcin para hacerlo acepta como entra.das. un arreglo a y un elementqx y regresa el

118 Estructuras de datos en C Reursin 119


n X y n X y n X y n X y n X y
llaman a la bsqueda binaria en una parte del arreglo. As, para que la funcin sea
recursiva, los lmites en los cuales debe realizarse la bsqueda tambin deben especi-
2 ficarse. La rutina se escribe de la siguiente manera:
3 3
4 4 4
5 5 5 5 binsrch(a, x, low, hi,gh)
6 6 6 6 6 int a[J;
irit x;
(a) (b) ( e) (d) (<)
int high, low;
{
n X y n X y n X y n ., y n X y int mid;

l
2



..


2

*
o
2
3



2

o
3
H (low > high)
return(-1);
.* .
3 3 3 *
4 4 * 4 * 4 4 mid - (low + high) / 2;
5
6.




5
6
5
6
* 5
6
*

5
6

return(x . a[mid] ? mid x < a(midJ ?
binsrch (a, x, low, mid-1) :
(O (g) (h) (i) (j) binsrch(a, x, mid+1, high))
} /* fin de binsrch */
n X y n X y n X y n X y n X y
.Cuando se llama a binsrch por primera vez desde otra rutina para buscar x en
un arreglo declarado por
l *' l
3 * 3 2 * * 2 * int a[ARRAYSIZEJ
4 4 4 2 4 2 4 2
5 " 5 5 * 5 5
6 6 6 6 6 en el cual los primeros n elementos estn ocupados, se llam mediante la siguiente
iristrucciff
(k) (l) (m) (n) (o)

i = binsrch(a, x, o, n-:L);
n .X y n X y n ,.X y n X y n X y

o Se invita al lector a seguir tanto la ejecucin de esta rutina como el proceso de


2 2 o la pila por medio del ejemplo de la seccin anterior, donde a es un arreglo de 8 ele-
4 2 4 2 4 2 3 mentos (n = 8) que contiene al, 3, 4, 5, 17, 18, 31, 33 en ese orden. El valor busca-
5 5 5 5 3 5 3 * do es 17 (x igual a 17). Ntese que el arreglo a se pone en la pila para cada llamada
6 * 6 * 6 6 6 recursiva, Los valores de /ow y high son los de los lmites inferior y superior del
(p) (q) (r) (s) (t) arreglo a, reSpectivamente.

Figura: 3.2;2 l.:.a pila de reursin. de !a funciff d~ Ffbonaccf:


En el curso del recorrido de la rutina binsrch, puede haberse notado que los va-
lores de dos parmetros a y x no cambian durante la ejecucin, Cada vez que sella-
ma a binsrch se busca el mismo elemento en el mismo arreglo; slo cambian los
lmites superior e inferior de la bsqueda. En consecuencia, parece un derroche, el
ndice idea para el cual [i]es igual ax o-:~ si no ~xi~te ta'. i. As la funcin binsrch
poner y eliminar esos dos parmetros de la pila cada vez que se llama a la rutina
podra llamarse por medio de una instrucc1on la s1gmente. recursivamente.
i = binsrch(a, x) Una solucin es permitir que a y x sean variables globales, declaradas antes del
programa por:
. d b, eda binaria de la seccin 3. l, tomn"
Sin embargo, al fijarse en el algontmo e, usqu . serva ue se transfieren dos int a[ARRAYSIZE);
dolo como modelo para una ruttna rec~rs1vain 7~e:es ~\ 9 de ~icho algoritmo slo int x,;
parmetros ms en las llamadas recursivas. as

120 Estructuras de datos en C 'F1cUrsin


121
:\ ! partir del examen del cuerpo de una de las rutinas en forma individual. La rutina a,
Se llama a la rutina por una instruccin com\l la siguiente:
1
!. parece llamar a otra rutina by es imposible determinar que se puede llamar a si mis-
ma de manera indirecta al examinar slo a a.
i = binsrch (O, n-1) Pueden incluirse ms de dos rutinas en una cadena recursiva._ As, una rutina a
En este caso, todas las referencias a a y x son a sus asignaciones globales de a Yx, puede llamar a b, que llama a c, ... , que llama a z, que llama a a. Cada rutina de la
declaradas al principio del archivo fuente. Esto permite a binsrch dar acceso a a Yx cadena puede potencialmente llamarse a si misma y, por lo tanto, es recursiva. Por
sin asignarles espacio adicional. Se eliminan todas las asignaciones mltiples Y libe- supuesto, el programador debe asegurarse de que un programa de ese tipo no genere
una secuencia infinita de llamadas recursivas.
raciones de espacio para esos parmetros.
Puede volver a escribirse la funcin binsrch como sigue:
Definicin recursiva de expresiones algebraicas

binsrch(low, high) Como ejemplo de cadena recursiva considrese el siguiente grupo recursivo de
int high, low;
definiciones:
{
int mid;
l. Una expresin es un trmino seguido por un signo ms seguido por un trmi-
if (low > high) no, o un trmino :solo.
return ( ~1); 2. Un trmino es un factor seguido por un asterisco seguido por un fartar, o un
mid = (low + high) / 2; factor solo.
return (x == a[midJ ? mid 3, Un factor es una letra o una expresin encerrada entre parntesis.
x < a[midl ? binsrch(low, mid-1) binsrch(mid+1, high)
I * fin de binsrch * I
Antes de ver algunos ejemplos, obsrvese que ninguno de los tres elementos
Mediante este esquema, se hace referencia a las variables a y x. por medio del atribu- anteriores est definido en forma directa en .sus propios trminos. Sin embargo, ca-
to extern y no se transfieren con cada llamada recursiva a binsrch. a y x no cambian da uno de ellos se define de manera indirecta. Una expresin se define por medio de
sus valores y no se ponen en la pila, El programador que desee hacer uso de binsrch un trmino, un trmino por medio de un factor y un factor por medio de una expre:
en un programa slo necesita transferir los parmetros /ow y high. La rutina podra sin. De manernsimilar, se define un factor por medio de una expresin, que se defi-
ne por medio de un trmino que a su vez se'define por medio de un factor. As, el
llamarse mediante una instruccin coi;no la siguiente:
conjunto completo de definiciones forma una cadena recursiva.
Demos ahora algunos ejemplos. La forma ms simple de un factor es una
i = binsrch(low, high); letra. As A, B, C, Q, Z y M son factores. Tambin son trminos, dado que un tr-
mino puede ser un factor solo. Tambin son expresiones, dado que una expresin
Cadenas recursivas puede ser un trmino solo. Como A es una expresin, (A) es un factor y, por lo tan-
to, un trmino y una expresin. A + Bes un ejemplo de una expresin que no es ni
. . Una fu~cin recursiva no necesita llamarse a s mism<1 de manera directa. En un trmino ni un factor, sin embargo (A + B) es las tres cosas. A *Bes un trmino
su lugar, puede hacerlo de manera indirecta como en el siguiente ejemplo:, Y, en consecuencia, una expresin, pero no es un factor. A* 3 + Ces un~ e~presin
b ( for,n,al parameters) que no es ni un trmino ni un factor. A * (B + C) es un trmino y una expresin, pe-
a(formal param~ters) ro no es un factor.
{ {
Cada uno de los ejemplos anteriores es una expresin vlida. Esto puede
mostrarse al aplicar la definicin de una expresin a cada uno. Considrese, sin em-
bargo, la cadena A + * B. No es ni una expresin, ni un trmino, ni un factor. Sera
b (rgumen ts); a ( argumen ts); instructivo para el lector intentar aplicar la definicin .de expresin, trmino y factor
para ver que ninguna de ellas describe a la cadena A + * B. De manera similar,
l*findea*I !*findeb*I (A + B *) C YA + B + C son expresiones nulas de acuerdo con las definiciones pre-
cedentes.
En este ejemplo la funcin a llama a b, la cual puede a su vez llamar a a, que puede . . Escribamos un programa que lea e imprima una cadena de caracteres y luego
llamar de nuevo a b. Asi, ambas funciones, a y b, son recursivas, dado que se llaman impruna "vlida" si la expresin lo es y "no. vlida" de no serlo. Se usan tres fun-
a s mismas de manera indirecta. Sin embargo, el que lo sean no es obvio a ciones para reconocer expresiones, tfminosy factores, respectivamente. Primero,

Estructuras de datos en C 123


122
sin embargo, presentamos una funcin auxiliar gelsymb que opera con tres par- I* (o ambas). Si expr (str, length, &pos) = = FALSE *I
metros: sir, length y ppos. sir contiene la entrada de cadena de caracteres; length I* entonces no hay una expresin vlida al inicio de *!
representa el nmero de caracteres en sir. ppos apunta a un entero pos cuyo valor es I* pos. Si pos < length puede que se encuentre una *!
la posicin en la sir de la que obtuvimos un carcter la ltima vez. Si pos < lenghl, I* expl'esin vlidaf comenzando en pos, *!
gelsymb regresa el carcter cadena str[pos J e incrementa pos en l. Si pos > = I* pero no ocupa la cadena completa . *I
!* fin demain *I
lenghl, getsymb regresa un espacio en blanco.

Las funciones factor y term se parecen mucho a expr excepto en que son res-
getsymb(str, length, ppos) ponsables del reconocimiento de factores y trminos, respectivamente. Tambin
char str[ l; reinicializan pos en la posicin que sigue al factor o trmino de mayor longitud que
int length, *ppos; se encuentra en la cadena sir.
{ L0s cdigos para estas rutinas se apegan bastante a las definiciones dadas an-
char e;
tes. Cada una intenta satisfacer uno de los criterios para la entidad que se reconoce.
if (*ppos < length) Si se satisface uno de esos criterios el resultado es TRUE (VERDADERO). Si no se
e str[*ppo'SJ; satisface ninguno, el resultado es FALSE (FALSO).
else
e = ' '; expr ( str, length, ppos)
(*ppos)++; char str[J;
return(c); int length, *ppos;
I* fin de gefsymb */ {
I* buscando un trmino *I
La funcin que reconoce una expresin se llama expr. Regresa TRUE (o 1) if (term(str, length, ppos) == FALSE)
retutn(FALSE); .
(VERDADERO) si una expresin vlida comienza en la posicin pos de sir y FAL-
I* Se ha eilcoD.trado un tr111no revisai el *I
SE (o O) FALSO en caso contrario. Tambin _vuelve a colocar pos en la posicin que
I* siguiente smbolo . *I
sigue a la expresin de. mayor longitud que puede encontrar. Suponemos tambin i f (getsymb(str, length, ppos) != +)
una funcin readslr que lee una cadena de caracteres, poni.endo la cadena en sir y su I* Se encontr la mayor expresin *I
largo en lenghl. I* (un solo trmino). Reposicionar pos para que *I
Una vez descritas las funciones expr y readslr, puede escribirse la rutina princi- I* seale la ltima posicin de *I
pal como sigue. La biblioteca estndar ctype. h incluye una funcin isalpha que es I* la expresin. *I
llamada por una de las funciones siguientes. (*ppos)--;
return(TRUE);
* fin de i * I
#include <stdio.h> I* En este punto, hemos encontrado un trmino *I
#include <ctype.h> I* Y un signo ms. Se deber buscar otro trmino. *I
#define TRUE 1 return(term(str, length, ppos));
#define FALSE o f* fin de expr *f
#define MAXSTRINGSIZE 100
main() La rutina lerm, que reconoce trminos, es muy similar, y la presentamos sin
{ comentarios.
char str[MAXSTRINGSIZEJ;
int length, pos; term(str, length, ppos)
readstr(str, &length)); char. str[ l;
pos= D; int length, *ppos;
if (expr(str, length, &pos) TRUE && pos >= length) {
printfC'%s", "vlida"); if (fac.tor(str, length, ppos) == FALSE)
else return(FALSE);
printf("%s", no vlida"); i f (getsymb(str, length, ppos) != '*') {
I* La condicin puede fallar por una de dos razones *I (*ppos)--;
i

!i

~
124 Estructuras de datos en C Recursin 125

i .i:-.
IIILJ
,
.......'
:tn
1
..

' return (TRUE);


I* fin deif. *f
Escriba una funcin recursiva en C para calcular mcd((x, y). Encuentre.un mtodo ite-
rativo para calcular dicha funcin.
return(factor(str, length, ppos,)); 3.2.3. Sea comm(n, k) que representa el nmero de comits diferentes de k personas que
!* fin de term *I pueden ser formados, dadas n personas de entre las cuales se puede elegir. Por ejemplo,
comm(4, 3) = 4, dado que dadas 4 personas A, B, C y O hay cuatro posibles comits
La funcin factor reconoce factores y debera ser ahora bastante sencilla. Usa de tres personas: ABC, ABO, ACD y BCD. Pruebe la identidad:
el programa comn de biblioteca isapha (esta funcin se encuentra en la biblioteca
ctype.h), que regresa algo distinto de cero si su carcer de parmetro es una letra Y comm(n,k) = comm(n - 1,k) + comm(n, - 1,k - 1)
cero (o FALSO) en caso contrario.
Escriba y pruebe un programa recursivo en C para calcular comm(n, k) paran, k > = I.
factor(str, length, ppos) 3.2.4. Defina una secuencia generalizada de Fibonac_ci defO y /1 como la secuencia gfib(/0,
/1, O), gfb(/0,fl, 1), gfib(/0,fl, 2), ... , donde
char str(J;
int length, *ppos;
{ gfib(fD, f1, O) fO
int e; gfb(fO, f1, ,L) f1
gfib(fO, f1, l) gfib(fO, f1, n - 1)
if ((e - getsymb(str, length, ppos)) !- ' ( 1 ) + g fib ( fD ,. f1, n - 2) if n > 1
return(isalpha(c));
return(expr(str, length, ppos) && Escriba una funcin en C recursiva para calcular gfib(/0,/1, n). Encuentre un mtodo
getsymb(str, iength, ppos) iterativo para calcular dicha funcin.
I * .fin de factor *I 3.2,5. Escriba una funcin recursiva para calcular el nmero de secuencias de n dgitos bina-
rios que no contehga dositnos seguidos. (Sugerencia: cakule cuntas secuencias de este
Las tres rutinas son recursivas, dado que cada una se puede Umar a s misma tipo existen que empiezan con O y cuntas que empiecen con 1,j'
de manera indirecta. Por ejemplo, si se sigue la accin del programa para la cad.ena 3.2.6. Una matriz de orden n es un arreglo n x n de' nmeros. Por ejemplo,
de entrada "(a* b + c * d) + (e*.(!) + g)" se encontrar que cada una de las tres
rutinas expr, term y factor se llama a s misma, (3)

es una matriz de 1 x I

EJERCICIOS
1 3
-2 8
3.2.1. Determine qu calcula la siguiente funcin recursiva de C. Escriba una funcin iterati-
va que cumpla el mismo propsito.
e$ una matriz de 2 x 2 y

func(n)
int n; 3 4 6
{ 2 ,-5 o 8
3 7 6 4
if (n -- O)
return (o); 2 o .9 ~1
return(n + func(n-1));
I* fin defunc *I es una matriz de 4 x 4. Defina el .menor de un elemento x en una matriz como la sub-
matriz formada al borrar el rengln y la columna que contienen una x. En el ejemplo
3.2.2. La expresin mn en C indica el residuo de la divisin de m entre n. Defina el fflximo precedente de una matriz de 4 x 4, el menor del elemento 7 es la matriz de 3 x 3
comn divisor (MCD) de dos enteros x y y mediante
4 6
gcd/x,y) y S ( J <= X && X % f 2 O 8
gcd/x,y) gcd/y,x) s (X < Y)
2 9 -1
gcd/x,y) gcd/y, X% J) de lo contrario

126 Estructuras de datos en C 127


'

1. '
1
!
Se observa con claridad que el orden de un menor de cualquier elemento es 1 menos I * cualquier grupo de instrucciones de C *I
que el orden de la matriz original. Denote el menor de un elemento a[i, )] por I * no cambie el valor de i *I
minor(a[i, j]). i = g(i);
Defina el determinante de una matriz a (escrita det(a)) recursivamente como sigue: I * fin de while *I
!* fin de iter *I
i. Si a es una matriz de 1 x 1 (x), det(a) = x.
ii. Si a es de orden mayor que 1, calcule el deter~inante de a de la siguiente manera:

a. Elija cualquier rengln o columna. Para cada elemento a[i, j] en ese rengln o COMO CODIFICAR PROGRAMAS RECURSIVOS
columna forme el producto
En la seccin anterior se vio cmo transformar una definicin o algoritmo recursivo
power(-1,i + j) a[i,jl * det(minor(a[i,Jl)) en un programa en C. Es mucho ms difcil desarrollar una solucin recursiva en e
pata resolver un problema especfico cuando no se tiene algoritmo. No es slo el
donde i y j son las posiciones de rengln y columna de ls elementos elegidos, a[i,j] programa sino las definiciones originales y los algoritmos los que deben desarrollar-
es el elemento que se escogi, det(menor(a[i, j])) es el determinante del menor de . se. En general, cuando encaramos la tarea de escribir un programa para resolver un
a[i, j] y power(m, n) es el valor de m elevado a la potencia n-esima. problema no hay razn para buscar una solucin recursiva. La mayora de los
b. det(a) = suma de todos esos productos. problemas pueden resolverse de una manera directa usando mtodos no recursivos.
(De manera ms conciSa, si n es el orden de a, Sin embargo, otros pueden resolverse de una manera ms lgica y elegante mediante
la recursin. En esta seccin se tratar de identificar los problemas que pueden resol-
det(a) power(-1, i + j) * a[i,jJ verse. de manera recursiva, se desarrollar una tcnica para encontrar soluciones
recursivas y se darn algunos ejemplos.
det(minor(a[i,Jl)), far any j Volvamos a examinar la funcin factorial. El factorial es, probablemente, un
* ejemplo fundamental de un problema que no debe resolverse de manera recursiva
o dado que su solucin iterativa es directa y simple. Sin embargo, examinemos los ele'.
mentas que permiten dar una solucin recursiva. Antes que nada, puede reconocerse
det(a) power(-1, i + j) * ali,jl un gran nmero de casos distintos que se deben resolver. Es decir, quiere escribirse
un programa para calcular O!, 1 ! , 2! y as sucesivamente. Puede identificarse un caso
j
det(minor(a[i,jl)), far any i) . "trivial" para el cual la solucin no recursiva puede obtenerse en forma directa. Es
*
el caso de O!, que se define como l. El siguiente paso es encontrar un mtodo para
Escriba un programa en C que lea a, la imprima en forma de matriz, e imprima el
resolver, un caso "complejo" en trminos de uno ms "simple", lo cual permite la
valor de det(a), donde det e!} una funcin que calcula el determinante de una matriz. reduccin de un problema complejo a uno ms simple. La transformacin del caso
3.2. 7. Escriba un programa recursivo en C para ordenar un arreglo a como sigue:
complejo al simple resultara al final en el caso trivial. Esto significara que el ca-
a. Sea k el ndice del elemento medio del arreglo.
so complejo se define, en lo fundamental, en trminos del ms simple.
b. Ordene los elementos hasta a[k] inclusive. Examinemos qu significa lo anterior cuando se aplica a la funcin factorial.
c. Ordene los elementos despus de a[k]. 4! es un caso ms complejo que 3 !. La transformacin que se aplica al nmero 4
d. Mezcle los dos subarreglos en uno solo ordenado. para obtener 3 sencillamente es restar l. Si restamos l de 4 de manera sucesiva llega-
Este mtodo se llama ordenamiento por intercalacin (merge sort). mos a O, que es el caso trivial. As, si se puede definir 4! en trminos de 3! y, en gene-
3.2.8. Muestre cmo transformar el siguierl.te procedimiento iterativo en uno recursivo. f(i) ral, n ! en trminos de (n - 1) ! , se podr calcular 4! mediante la definicin de n ! en
es una funcin que regresa un valor lgico basado en el valor de i, y g(i) es una funcin trminos de (n - l)! al trabajar, primero hasta llegar a O! y luego al regresar a 4!.
que regresa un valor con el mismo atributo que i. En el caso de la funcin factorial se tiene una definicin de ese tipo, dado que: ,
p

iter(n) n! = n * ('n - 1) !
int n;
{ As, 4! = 4 * 3! = 4 * 3. * 2! = 4 * 3 * 2 * 1! = 4 * 3 * 2 * 1 * O! = 4 3 * 2] *] = 24.
int i Estos son los ingredientes esenciales de una rutina recursiva: poder definir un
i = n; caso. "complejo" en trminos de uno "ms simple" y tener un caso "trivial" (no re-
while(f(i) TRUE) {
cursivo) que pueda resolverse de manera directa. Al hacerlo, puede desarrollarse una

128 Estructuras de datos en C Recursin 129


A B ('
solucin si se supone que se ha resuelto el caso ms simple. La versin en C de la
funcin factorial supone que est definido (n - l )! y usa esa cantidad al calcular n !.
Veamos cmo se aplican esas ideas a otros ejemplos de las secciones previas.
En la definicin de a* b, es trivial el caso de b = l, pues a* bes igual a a. En gene-
ral, a* b puede definirse en trminos de a* (b - 1) mediante la definicin a* b = a
* (b - 1) + a. De nuevo, el caso complejo se transforma en un caso ms simple al
restar l, lo que lleva, al final, al caso trivial de b = 1. Aqu la recursin se basa ni-
camente en el segundo parmetro, b.
En el caso de la funcin de Fibonacci, se definieron dos casos triviales: /ib(O)
= O y /ib(l) = l. Un caso complejo fib(n) se reduce entonces a dos ms simples: Figura 3.3.1 Planteamiento inicial de las Torres de Hanoi.
fib(n - l) y fib(n - 2). Esto se debe a la definicin de/ib(n) comofib(n - 1) +
fib(n - 2), donde. se requiere de dos casos triviales definidos de manera directa,
/ib(l) no puede definirse como/ib(O) + /ib(-1) porque la funcin de Fibonacci no -~hora se Ver si_ ~e puede desarrollar' una solucin. En lugar de. concentrar la
est definida para nmeros negativos. aten~10n en una so_luc10n para cinco discos, considrese el caso general den discos.
La bsqueda binaria es un caso interesante de recursin. La recursin se basa Supongase que se tiene uni, solucin paran - 1 discos y que, en trminos de sta se
en el nmero de elementos del arreglo que debe buscarse .. Cada vez que se llama a pueda plantear la solucin para n - l discos. El problema se resolvera enton~es.
la rutina de manera recursiva, el nmero de elementos que se busca se divide en dos Esto sucede. porq~e en el caso trivial de un disco (al restar 1 den de manera sucesiva
partes (lo ms parecidas en tamao). El caso trivial es aquel en el cual no hay ningn seproduwa, al final, 1) la solucin es simple: slo hay que mover el nico disco del
elemento con el cual comparar el que se busca o ste se encuentra en .medio del P_~ste A a C. ,:'-si se ha~r _desarrollado una solucin recursiva si se plantea una solu-
arreglo. Si /ow > high, se cumple la primera de las d.os condiciones-! es regresado. c1on para n discos en termias de n c._ l. Considrese la posibilidad de encontrar tal
Si x "" .a[mid), se cumple la segum:!a y se regresa a mid. En el caso ms complejo de relac1on. Para el caso de cinco discos en particular, supngase que se conoce la for-
high - /ow + l elementos para buscarse, la bsqueda se reduce a tomar lugar en ma de mover cuatro de ellos del poste A al otro, de acuerdo con las reglas. Cmo
una de dos regiones divididas, puede completarse entonces el trabajo de mover el quinto disco? Cabe recordar que
hay 3 postes disponibles.
l. la mitad inferior del arreglo, de low a mid -1 S~pngase _que se supo cmo mover cuatro discos del poste A al C. Entonces,
2. la mitad superior del arreglo, de mid + 1 a high se podra mover estos exactamente igual hacia el B usando el C como auxiliar. Esto
da como :esultado la situacin representada en la figura 3.3.2a. Entonces p~dr mo-
As, un caso complejo (una gran rea donde debe buscarse el elemento) se reduce a v~;se el disco may~r de A a C(figura 3.3.2b) y por ltimo aplicarse de.nuevo la solu-
uno ms simple (un rea donde debe buscarse el elemento cuyo tamao, es aproxima- c1on para ~~atrn,d1scos para moverlos de B a C, usando el poste A, ahora vaco, de
damente, de .la mitad del rea original). Esto se reduce, por ltimo, a la comparacin forma auxiliar (figura 3.3.2c). Por lo tanto, se puede establecer una solucin recursi-
con unsolo elemento (a[mid)) o a buscar en un arreglo que no contenga ninguno. va al problema de las Torres de Hanoi como sigue:
Para mover n discos de A a C usando B como auxiliar:
El problema de las Torres de Hanoi
L Si n = = , mover el disco nico de A a e y parar.
Hasta aqu se han visto definiciones de recursin y se ha examinado cmo se 2. Mover el disco superior de A a B n - l veces, usando C como auxiliar.
ajustan a la pauta establecida. Se ver ahora cmo pueden usar tcnicas recursivas
3. Mover el disco restante de A a .
,,; para lograr una solucin lgica y elegante de un problema que no se especifica en
( trminos recursivos. El problema es el de "las Torres de Hanoi", cuyo planteamien- 4, Mover os discos n - l de B a C usando A cono auxiljar.
to inicial se muestra en la figura 3.3.L Hay tres postes: A, By C. En el poste A se
ponen cinco discos de dimetro diferente de tal manera que un disco de dimetro ..... Ci1 tda S~Uridad este algoritmo producir ur solucin correcta para cual-
mayor siempre queda debajo de uno de dimetro menor. El objetivo es mover los quier valor den. St n = = 1, el paso I ser la solucin correcta. Si n = = 2 se sabe
. ces q~e
ehton hay
una so 1 ucton
" paran
- 1= = . l,
de manera tal que los. .pasos
' 2 y4
cinco discos al poste C usando el B como auxiliar. Slo puede moverse el disco supe-
rior de cualquier poste a otro poste, y un disco mayor jams puede quedar sobre uno . se.eJec~mr~n en for~acorrecta. De manera anloga, cuando n ~ = 3, yase habr
menor. Considrese la posibilidad de encontrar una solucin. En efecto, ni siquiera producido una soluc,.on paran ~ l = = 2, por lo que los pasos 2 y 4 pueden ser eje-
cutados. De esta forma se puede mostrar que la solucin funciona para 11 = = 1 2
es claro que exista alguna. ' '

Estructuras de datos en C . RC~fSiri 131


130
e el programa con exactitud. Es insuficiente, un planteamiento del problema como:
A B
"resolver el problema de las Torres de Hanoi". La especificacin de tal problema
significa por lo general que, adems del programa, se deben disear las entradas y
salidas, de manera que correspondan a la descripcin del problema.
El diseo de entradas y salidas es una fase importante de la solucin y ste debe
recibir tanta atencin como el resto del programa. Existen dos razones para ello. La
primera es que el usuario (que es en ltima instancia quien evala y emite juicios
acerca del trabajo) no ver el refinado mtodo que se incorpor al prograrna, si-
no que se esforzar mucho en descifrar la salida o en adaptar los datos de entrada a
los usos particulares de entrada establecidos. El descuido de no convenir a tiempo
(:.i)
los detalles de entrada y salida ha sido causa de mltiples aflicciones tanto para los
programadores como para los usuarios. La segunda razn es que un leve cambio en
/J e el formato de entrada o salida puede facilitar en gran medida el diseo del progra-
ma. As, el programador puede hacer el trabajo mucho ms fcilmente si tiene la
capacidad de disear un formato de entrada y salida compatible con el algoritmo.
Claro que estas dos consideraciones -la de la conveniencia para el usuario y la del
programador- entran en conflicto con frecuencia, por lo que es necesario encontrar
un justo medio. Sin embargo, tanto el usuario como el programador deben partici-
par de manera activa en las decisiones acerca de los formatos de entrada y salida .
. Ahora puede procederse al diseo de entradas y salidas de este programa. La
nica entrada necesaria es el valor de n, el nmero de discos. Por lo menos, ste
(b)
puede ser el punto de vista del programador. Es probable que el usuario desee los
nombres de los discos (como u rojo", "verde", "azul", etc.) y los nombres de los
postes (como "izquierdo", "derecho" y "central"). El programador quiz pueda
- A B e convencer al usuario de que nombrar los discos como l, 2, 3, ... n y postes como
A, B, Ces igual de conveniente. Si el usuaries obs(inado, el programador puede
escribir unapequea funcin pra convertir los nombres del usuario en los suyos y
viceversa.
Una forma razonable para la salida podra ser una lista de instrucciones como:

mover disco nnn del poste yyy al poste zzz

donde nnn es el nmero de disco que se debe mover, y yyy y zzz son los nombres de
los postes implicados en dicho movimiento. La accin que deber emprenderse para
(t:)
obtener una solucin consiste en ejecutar cada una de las instrucciones de salida. en
Figura 3.3.2 Solcin recursiva al problema de las Torres de Hanoi. el orden en que aparecen en la misma.
El programador decide entonces escribir la subrutina towers (en la que a pro-
psito es ambiguo acerca de los parmetros, en este punto) para imprimir la salida
3, 4, s, ... hasta el valor para el que se desee encontrar una so!uci~. Advirtase que la antes mencionac!a. El programa principal sera
solucin se desarroll mediante la identificacin de un caso tnv1al (n = = 1) Y una
solucin para el caso general y complejo (n) en trminos de un cas.o ms sim~le (n - l); main ()
Cmo se puede convertir esta solucin en un progrnm~ en lengua~ C? Aqu1 (
ya no se tr_ata_ con una funcin matemtica como factonal, sm? con a,c~10neS, con- int n;
cretas como "mover un disco". Cmo deben representarse dichas acc_10nes en la
computadora? El problema no se especifica por completo. Cules son las e~t,adas scanf( 11 %d11, &n);
del programa? Cules deben ser las salidas? Siempre que se le pida .escnbtr un towers(parameters);
I* fin de main *I
programa, se deben recibir instrucciones especficas sobre lo que se espera que haga

Estructuras de datos en C Recursin 133


132
Bajo la suposicin de que el uwario est satisfecho con denominar los discos l,
2, 3, ... , n y los postes A, B, C, cules sern los parmetros de towers? Es evidente
!* e .como auxilitr *I
towers(n-L, frompeg, ~rixp~~, top.eg);
que debe incluir a n, el nmero de discos que debe moverse. Esto no slo incluye in-
formacin acerca de cuntos discos hay, sino tambin de cules son sus nombres. El
/* Mov_er el cfjsco restante de A a e,
1
*
pr.intf("/n%.s%d%s%C%s%.c%'.', ' mover disco", n;, ".~el poste",
programador advierte entonces que en el algoritmo recursivo se deben mover i1 - 1
frol!lpe(J', \\al poste11 , to_peg).
discos mediante una llamada recursiva a towers. As, en la llamada recursiva, el I* Mover n-.1 discos de B _hacia C empleando * '
primer parmetro de towers ser n - l. Pero esto implica que los ,i - f discos ! *. _ A como _a_uxili~r *-
superiores tienen la numeracin l, 2, 3, ... , n - 1 y que el menor de todos tiene el towers(n-1, auxpeg, topeg, irompeg)
I * iIJ. Q.e fowers * /" '
nmero l. Esto es un buen ejemplo de.cmo programar la conveniencia luego de
determinar la representacin del problema. A priori, no hay razones para etiquetar
el disc ms pequeo con el nmero l; de manera gica,podda haberse etiquetado Trazar 1~ acdn dd programa anterior .cuando ste .lea el valor 4 paran. s. e
d
el disco .ms grande con nmero 1 y el ms pequeo con n: Sin embargo, C()mO es 0
clebl:! ten.er cu.idado de. t,:,lllar ~n. cuenta los valores cambi.ntes de los parmetros
f.(qmpeg, auxpeg Y topeg y venficar q~e produzca la siguiente salida:.
to conduce a un programa ms simple y directo, se opt por eticetar los dis.cos de
tal manera que al disco menor le corresponda el nme~o lllenor. ;Mover disco 1 d43l poste A al poste B
Cules son los otros parmetros de towers? A primera vist~, parecera qu 110 MVer diiC~ 2 .~el pOSt A al p5>St~ e
se necesitan parmetros adicionales, de.bido que los postes tienen denominacin de MOVer -diSco 1 d.e"I p0Ste B al posfe e
A., B y C por omisin. Sin embargo,. una observacin ms detaHada de la soludn _!tf;o_v_9r .~,sco 3 d~l poite A 'al !)6Ste B
recursiva da pauta para comprender que en las llamadas recursivas no s trasiadan MoVer diSco 1 clel Poste .e a:1 P_of;t8 A
C
los discosde a usando B como auxmar, sino de A a B [!sancto C (paso 2) o de B a 'bf'~.~~~ di~~~ 2 4il_'p(),Sf~ e eiI .poSte B

C usando A (paso 4). Por lo tanto, se incluyen tres parmetros ms en t~wers. El pd- --~9Y,er .<;_i~c::o 1 .d.~l poste A al p-~ste B
mero,frompeg, representa el poste del que se retiran los discos; e segundo, tdpeg, el .}f~!';f~l di_~.~-. ~ del P.oSt~ ~ a1 post~ e
poste en que se colocarn los discos; y el tercero, duxpeg, el poste auxiliar.Esia si~ _:;M,Y~_r. d_i~co 1 ~:l _p_O~tti! B a1 po,Ste e
tuacines tpica de las rutinas recursivas; se requieren parmetros adkionales para
.M.~Y-~r c;1~s6~ 2 .<;l~l pO~te B l Psh3" A
~9ver dJSC9 1 del poste C al pose A
tratar la sit.uacin de llamad recursiva. Un. ejemplo de esto puede observarse ~n el ~P?rr_d$C9 3 .~~l' pOsie B l'post e
progcama de bsqueda binaria, en elque serequirieron los parmetrns low y high. Movr di.so 1 d~l 'iloS'te A al poste J3
El prngrama completo para resolver el problema de las Trres de Hancii, si- , - ''i;(OV'er \So 2 del -'Poste A .al posfe e
guiendo muy de cerca la solucin recursiva, se puede escribir coirto sigue: )doyr aiscp 1 del pSt B al Poste C

#include <stdio.h> Verificar _que la solucin


- anterior
: , , _funciona
. - en efecto
.. . 1...
y que ,no y10 e 1as
. . reg las.
main(_)
Convers}n de prefija a postfija por medio de la recursl.n
{
int n; , A ~ontinuacin se examina Qtro problema para el cual ta solucio'
la.mas directa f d E . n recursiva es
sb~f(i 1 %dtt, &h)!
. f' <.
t
Y re in~
n es_t_e proble!11~ se trata de convertir una expresin prefija
towers ( h, 1 A, , 1 e i , , B, ) ;
poS iJa, Las notaciones Pr':':iia Y postfiJa se analizaron en el captulo anterior. En
resu111e~,. las notaciones ~refiJa y postfija son mtodos para escribirna expresin
I* fin de inin I * p1ate;atica sm usar parentesis. En la not.acin prefija cada operador precede de
toWets(ri, frompeg, topeg, uxpeg) m1:1e iatQ a sus operandos; en la postfija, les sigue. A manera de recordatorio ense-
int n; ~uidasf.~ prese.nta~ ~-nas cuantas expresiones matemtkas (in fijas) con sus equi~alen-
. es me i.ias y postf11as:
,\. char auxpeg, frotnpeg, topeg;
{
I* I f Si eS slo Uz:i discp., efectllr inoviinienfo y regresar */ -:-:c--::---i,_f._iJ_a___________ prefija
if ( n == 1) 1
A+B +AB
printf("/n%s%d%s%cS%c%", "mover discO i di p6Ste 11 1 A AB +
+B*C +A*BC +
frohipeg; "iil pbSt", i6P~it . .
:A * (B + C) , A + BC
ABCo
return; .A. s e ,:\B,C+ *
* + AB * e+
+ , ABC
I * Hn de if *I A + s iiC+D-E,F -++ABCD,EF
I * MOv_er ls n-1 discos de arriba de A a B, usjd *I .(A +B) (C + D E"
* - )* F ABC + D + E F , -
+ AB - +CDE F
--------.,------~:__~.:.::::.:_:_ __.'.'.A"_B_+:::..':C::_D'._.':+:._'E:._::-:_:_:F'.._''__
134 Est'ructuras de dtos eri
1.3.5
La mejor forma de definir postfija y prefija es por medio de la recursin. Si se coloca en a la cadena "abcdexyz" (esto es, la cadena constituida por todos los ele-
asume que no hay constantes y que slo las letras individuales son variables, una mentos de a segwdos por todos los de b). Tambin son necesarias las funciones
expresin prefija es una letra individual, o un operador seguido de dos expresiones strlen y substr. La funcin str!en (str) da como resultado el largo de la cadena sir L
prefijas. Es posible definir de manera similar una expresin postfija como una letra funcin substr(s 1, i, J, s2) forma la cadena s2 a partir de la subcadena des co~en~
individual, o un operador precedido por dos expresiones postfijas. Las definiciones zando en la posicin i que contiene j caracteres. Por ejemplo, despus de ejecutar
anteriores suponen que todas las operaciones son binarias y esto es, que cada una . substr(''abcd'', 1, 3, s), ses igual a ''be''. Las funciones strcat, strlen y substr, son
requiere de dos operandos. La suma, la resta, multiplicacin, divisin y la exponen- por 19 general funciones estndar de biblioteca para cadenas en c.
ciacin son ejemplos de operaciones binarias. Resulta fcil prolongar las defini- Antes de transformar el algoritmo de conversin a un programa en C es nece-
ciones de prefija y postfija anteriores a las operaciones unarias, como el factorial o sario examinar sus entradas y salidas. Se desea escribir un procedimient~ convert
la negacin, pero por cuestin de simplicidad no se har aqu. Es necesario verificar que acepte una c_adena de caracteres, la que representa una expresin prefija en la
la validez de cada una de las expresiones postfijas y prefijas anteriores mostrando que todas las vanables son letras individuales y los operadores permisibles son '+'
que se apegan a las definiciones y asegurndose de que es posible identificar los dos ,._,, '*' y '/'. El procedimiento genera una cadena-postfija equivalente a la caden~
operandos de cada operador: parmetro prefija.
Ms adelante se usarn estas definiciones recursivas, pero antes hay que regre- Supngase la existencia de una funcinfind que acepta una cadena y da como
sar al problema inicial. Dada una expresin prefija, cmo puede transformarse sta res~ltado un entero que es. del mismo tamao que la expresin previa ms larga con-
a postfija? Es posible identificar de inmediato un caso trivial: cuando una expresin tenida en la cadena de entrada desde el principio de la misma. Por ejemplo,Jind("A
prefija consiste de una sola variable, sta equivale a su propia expresin postfija. Es + CD"_) d_a como resultado 1, ya que "A" es la cadna prefija ms larga encontra-
decir/, una expresin como A es vlida tanto como prefija como postfija. da al pnnc1p10 de "A + CD" .find(" + ABCD + OH") da como resultado 5, ya
Considrese ahora una cadena prefija ms larga. Si se supo cmo convertir que"+ * ABC" es la cadena prefija ms larga que comienza donde lo hace la cade-
una cadena prefija corta a postfija, es posible convertir esta cadena ms larga a na dada. Si no existe una cadena prefija que comience donde inicia la cadena de
postfija? La respuesta es s, con una excepcin. Cualquier cadena prefija que conste entrada Jind regresa O. (Por ejemplo, find("* + AB") es igual a O.) Esta funcin
de ms de una sola variable contiene un operador y un primer y un segundo operan- se usa par~_identificar el primer y el segundo operandos de un operador prefijo. con-
dos (cabe recordar que se asumieron nicamente operadores binarios). Si se asume vert tamb1en llama a la funcin de la biblioteca isa!pha, la que determina si su
la posibilidad de identificar el primero y el segundo. operandos, que son. necesa- parme~~o es una letra. Si se asume la existencia de la funcin Jind, una rutina de
riamente ms cortos que la cadena original, se puede transformar entonces la cadena convers1on puede escribirse como sigue:
prefija larga a postfija convirtiendo en primer lugar el primer operando a postfija;
luego el segundo, que se coloca detrs del primero, y por ltimo, se coloca el opera- convert (prefix, postfix)
dor inicial detrs de stos en la cadena resultante. De este modo se desarrolla un char prefix[J, postfix[J;
algoritmo recursivo para convertir una cadena prefija a postfija, con la salvedad de {
que en una expresin prefija se debe especificar un mtodo para identificar los ope- char opnd1[MAXLENGTH], opnd2[MAXLENGTH];
randos. El algoritmo puede resumirse como sigue: char post1[MAXLENGTH], post2[MAXLENGTH];
char temp[MAXLENGTH];
l. Si la cadena prefija es una sola variable, sta es su propia equivalencia postfija. char op[2];
int length;
2. op ser el primer operador de la cadena prefija.
in t i, j, m, n;
3. Encontrar el primer operando, opndl, de la cadena. Convertirlo a postfija y
denominarlo post l. if ((length - strlen(prefix)) -- 1)
4. Encontrar el segundo operando, opnd2, de la cadena. Convertirlo a postfija y if (isalpha(prefix[OJ)) {
denominarlo post2. I* La cadena prefija es una sola letra *I
S. Concatenar post l, post2 y op. postfix[Ol prefix[DJ;
postfix[1J = , \01;
return:
Una operacin necesaria en este programa es la de la concatenacin. Por /=lfindeif *I
ejemplo, si dos cadenas representadas por a y b representan las cadenas "abcde" y prin tf (" \ ncadena prefja ilegal ") ;
"xyz" respectivamente, la llamada a la funcin exit(1\;
!* fin de il *!
strcat(a, b)
,.
I* La cadena prefija contiene ms de un
carcter. Extraer las longitudes de JOs
*I
*I

136 Estructuras de datos en e Recursin


137
/* dos operap.dos y del ope!ador. *f obvio si se considera que la subcadena de la cadena dada c.omienza en una posicin
op[OJ prefix[DJ; determinada.
op[1J 1 \0 1 ;
Advirtase que esta prueb~ prnporciona un mtodo recursivo para encontrar
substr(prefix, 1, length-1, temp);
m = find(temp);
una expresin prefija en una cadena. Ahora incorprese e~te m.todo a la funcin
substr(prefix, m+1, lngth-m-1, temp);
jind:
n = find(temp);
if ( (op[Ol != '+ 1 && op[Ol != 1 - 1 && op[OJ != '* 1 && find(str)
op[OJ != 1 / 1 ) 11 ( m == o ) 1 1 ( n == char str[J;
1 1 (m+n+1 != length) {
printf("/ncadena prefija ilegal"); char temp[~AXLKNGTHJ;
exit(1); int length;
I* fin de if *! i,_nt .i,_ j, m, n;:
substr(prefix, 1, m, opnd1);
substr(pref.ix, m+1, n, opnd2); if ((leqgtb ~ strlen(str)) O)
convert(opnd1, post1); return (O);
convert(opnd2, post2).~ if (isalpha(str[O]) != O)
strdat(post1~ post2); I* El prirr;.er car4cter es una letra. *I
strcat(postL, op); I* Esa letra .es _la su.bca<i;ena *I
substr(post1r O, lerigth, pdStfix); I* incjal *I
/ *- fin de convert */ return ( 1) ;
I * .De .otra ma.nera, encontrar el prlller ope_rando */
if (strlen(str) < 2)
. Hay que advertir que se han incorporado varias verificaciones dentro de con, .return (O);
vert para asegurar que el parmetro sea una cadena prefija vlida. Uno de los tipos substr(str_, 1, length-1, temp);
de errores ms difciles de detectar es aquel en que los errores son resultado de una m = find(temp);
entrada no vlida y del descuido del programador en verificar .su validez. if (m == o 11 strlen(str) == m)
I* no es un operando .prefijo _v.ido .* !
Ahora prestemos atencin a la funcin find, la que acepta una cadena de ca-
I* o +20 hay segundo ._operando *I
racteres y una posicin inicial y da como resultado la longitud de la cadena prefija retur.n (o);
mayor contenida en la cadena de entrada dada, comenzando efi dicha posicin. La substr ( str, m+1, length-m-1, temp);
palabra "mayor" en esta definicin es superflua, ya que hay a lo ms una subcadena n = find(temp);
que comienza en una posicin determinada de una cadena que es una expresin pre-; .if (n == O)
fija vlida. retu.rn (O);
Primero se muestra que hay a lo sumo una expresin prefija vlida que co- r.e;turn ( m+n+1) _;
mienza al principio de una cadena. Para comprobar esto, debe advertirse que se I* fin de find *!
cumple de manera trivial en una cadena I de largo, y supngase que es verdadero pa-
ra una cadena corta. Entonces una cadena larga que contenga una expresin prefija Para verificar la comprensin de cmo funcionan estas rutinas, hay que trazar sus
como subcadena inicial debe comenzar con una variable, en cuyo caso la variable e.s acciones sobre expresiones .vlidas y no vlidas. Ms importante an. es comprobar si
la subcadena deseada, o con un operador. En caso de borrar el operador inicial, el se entendi cmo fueron desarrolladas y cmo el anlisis lgico condujo a una solu-
resto de la.cadena ser ms corta que la cad.ena original y, en consecuencia, tener:a cin recursiva natural que se pudo tr:iducir en forma directa a un prog,ama en C.
lo sumo una expresin inicial prefija. Esta expresin es el primer operando del ope-
rador inicial. De manera anloga, la subcadena restante (luego de borrar el primer
operador) slo puede tener una subcadena inicial simple, que es una e~presin prefk
ja. Esta expresin debe ser el segundo operando. As se identifica de manera nica el
_J;~.:,J. Supo11ga QL!e se a~arji . otra _excep_cin al problema de las Torres _de. Hanbi: que un
operador y los operandos de la expresin prefija comenzando en el primer carcter'
di.sco_no pueda desc11_sar.sobre otr~ que _!9 rebase en ms de un~'talla (por. ejemplo: el
de una cadena arbitraria, en caso de que exista tal expresin. Ya que existe a lo mS, disco l sl_podr descansar sobre el 2 o en. la.,bas~; el_dos .sobr_e. el.tres. o:_en:1.a base,y
una.cadena prefija vlida que comienza al principio de cualquier cadena, hay a los.u- _as . sucesivamente) .. Por.qu no.funciona la solucin del.texto? Qu~_es lo que o
mo una cadena que comienza en cualquier posicin de una cadena arbitraria. Esto es: f4-nciona c,n la !gica,.que condu~e a ello de ~cuerdb ,:;orl J_as nuv.as:~eglas?

138 :Re.c,ursin 139


~ ..
3.3.2. Pruebe que el nmero de movimientos que ejecuta towers para mover n discos es dos los ndices del arreglo con i < = j. Desarrolle un procedimiento recursivo que de-
igual a 2n-1. Es posible encontrar un mtodo con menos movimientos para resolver termine i y j de modo que contigsum(i,j) sea aumentada al mximo. La recursin de-
el problema de las Torres de Hanoi? Encontrar dicho mtodo para algunas no probar ber considerar las dos mitades del arreglo a.
que no existe ninguno. 3.3.9. Escriba un programa recursivo en C para encontrar el k-simo elemento ms pequeo
3.3.3. Defin~ una expresin postfija y prefija que incluya la posibilidad de operadores una- de un arreglo a de nmeros, eligiendo cualquier elemt:nto a[i] de a y seccionando a en
rios. Escribir un programa para convertir una expresin prefija que contenga el ope- aquellos elementos ms pequeos, iguales y mayores que a[i].
rador de negacin unaria (representada por el smbolo'@') a postfija. 3.3.10. El problema de las ocho reinas es colocar ocho reinas en un tablero de ajedrez de ma-
3.3.4. Reescriba la funcin find del texto de manera que no sea recursiva y que.cakula la nera que ninguna ataque a las dems. El siguiente es un programa recursivo para re-
longitud de una cadena prefija contando el nmero de operadores y los operandos de solver dicho problema. board es un arreglo de 8 x 8 que representa el tablero.
una sola letra board[i]U) = = TRUE (es verdadero) si hay una reina en la posicin [i]U], y FALSE
3.3.5. Escriba una funcin recursiva que acepte una expresin prefija que consista en opera- (falso) en caso comrario. good() es una funcin que da como resultado TRUE (verda-
dores binarios y operandos enteros de un solo dgito y que d como resultado el valor dero) cuando dos reinas del tablero no se atacan entre s, y FALSE en caso contrario.
de la-expresin. Al final del programa, la rutina.drawboard() muestra una solucin del problema.
3.3.6. Considere el siguiente procedimiento para convertir una expresin prefija a postfij3..
cnv(prefix, postfix) se usara para llamar la rutina. static short int board [8J(8J;
#define TRUE 1
conv(prefix, postfix) #define FALSE o
char prefix[J, postfix[J;
{ ma in ( )
char first[ 2 l; {
char t1(MAXLENGTH), t2(MAXLENGTHJ;
in t i, j ;
first[OJ = prefix[DJ;
first[1J = 1 \0 1 ; for(i=D; i<8; i++)
substr(prefix, 1, strlen(prefix) - 1, prefix); for(j=O; j<8; j++)
1f (first(Ol == 1 + 1 11 first[Ol '*' board[i](j] = FALSE;
11 first[Ol 1 - 1 11 first[OJ == '/') { if (try(O) == TRUE)
conv(prefix, t1); drawboard();
conv(prefix, t2); !* findemain *I
strcat(t1, t2);
strca t ( t1, first) ; try(n)
substr(t1, o, strlen(t1), postfix); int n;
return; }
!* fin de if *I int i;
postfix[Ol = first[DJ; for(i=D; i<8; i++) {
postfix(1l = \0'; board(nl(il = TRUE;
I* fin de conv *I if (n == 7 && good() == TRUE)
return(TRUE);
if (n < 7 && good() == TRUE && try(n+1) TRUE)
Explique cmo funciona ei procedimiento. Es mejor o peor que el mtodo del texto? return(TRUE);
Qu ocurre cuando se llama la rutina mediante una cadena prefija no vlida como board[n)(il = FALSE;
entrada? Se puede incorporar una verificacin para cadenas no vlidas en convert? !* fin de for *I
Es posible disear semejante verificacin para el programa de llamada despus de re.turn (FALSE);
que convert regresa? Cul .es el valor de n tras el regreso de convert? f* fin de try *!
3.3.7. Desarrolle un mtodo recursivo (y progrmelo) para calcular el nmerode maneras
diferentes en que un nmero entero k pueda escribirse como una suma, en la que cada Determinado el tablero (board) al. momento de llamarla, la funcin recursiva _11:v da
uno de los operandos sea menor (lue n. como resultado. TRUE, Siempre que es posible, a fin de agregar reinas en los renglo-
3.3.8. Considere un arreglo a que contenga enteros positivos y negativos. Defina nes del n al 7 para alcanzar la solucin. try da como resultado FALSE si no hay una
contigsum(i,j) como la suma de los elementos contiguos desde a{i] hasta al/] para to- solucin que tenga reinas en las posidones de board que ya contienen TkUE. Si el re-

140 Estructuras de datos en C Recursin 141


sultado es TRUE, la fucin tambi~n agi:g:i reinas en los renglone~ del n al 7 para ejemplo) que puede correr miles de veces, el costo de recurrencia .
generar una solucin. para los recursos limitados del sistema. es un carga pesada
Escrib.a las funciones good y drawboard anteriores, y verifique que el programa gene-
re una solucin.
. Por lo tanto, se puede disear un programa para incor orar una . ,
cursiva que reduzca el costo de diseo y certificacin y luegop. . soluc1on re-
(La fdea Que hay detrs de la sol1;1Gin es la siguiente: boardrepresenta ,la situacin ' 'h . ., , convertir con cutdado
global durante la tentativa de encontrar tina solucin ..El siguiente paso en la .bs-
d 1c . o programa en una vers1on no recursiva que se pueda ct e
1 .. d usar tano. orno se ver
queda de la solucin .se escQge arbitrariame11te (se coloca una reina en I siguiente en a ,eiecuc10n. . e tal conversin muchas veces .es post"ble a rtcar partes. de la'
posicin no probada del relgln n). Luego se comprueba de manera recursiva si es po- creac10n recursiva que son superfluas en una aplicaci rt entt
sible generar una solucin que i.ncluya ese paso .. Si lo es, se regresa. Si no, se explora a ci, reducir la cantidad de trabaio que debe desem pe- parl icu 1ar Y, en consecuen-
. . .. nar e programa.
la inversa a partir del s,iguiente paso intentadci -:.bQard[n][t} =FALSE-y se intenta Antes de examinar las acciones de una rutina recursiva habra' que
otra posibilidad. Este ritodo?e "llama. backtracking o bsqueda c_qn retroceso.) . tra's y exa minar
1a acl'.1on
""' d e una rutma
. no recursiva M., 'd 1, . , regresar
d b . as a e ante se podra ver qu
3.3. t l. Un arreglo maze- de lO x IO, de ceros .Y unos,- representa tm laberinto en el que un ~ec_~msmo - e .e a_gre~arse P~,ra sustentar la recursin. Antes de proceder; se debe
viajero debe encontrar un camin de maze[OJ[OJ a maze[9][9]. El viajero puede mo- adoptar la s1gu1ente convenc1on. Suponga que se tiene la instruccin
verse- Qe un cuadrado . _.otf9 adyacellte en la misma cOimna o _rengl_Il, pero no
puede saltar ningn cuadrado ni mverse en diagonal. Adems, 110 pede moverse a
ningn cuadrado que contenga un .i. maze[O][OJ y mm:e[9][9] co~tienen ceros ..Escri- rout(x);
ba ulla rutina que acepte tal laberinto (/naze) e imprima un m~nsaJe' de que no existe
camino a travs del "iaberinto o una lista ct"e posiciones repreSer1t.nd ~ c~njl'lo de dnde -otllqueda definida como una funcin por med 10 d e enea
bezam1ent:

[O][OJ a .[9][9].
.rout(a)

3.4. .SIMULACION OE l'IECUR.SION \eS as/g~ada Como un argumento (de la funcin de llamada) y a CO b . ,
(de la funcin llamada). m un pa, ametro

En esta seccin se examinan con ms detalle algunos de losmecanismo.s usados para .... C)u~ pasa. cuando se llama una funcin? La accin de llamar u ar .,
puede dtvtdtr en tres partes: n uncton se
crear la recursin a fin de poder simular estos mecanismos mediante tcnicas no re-
cursivas. Esta actividad es importante.por varias razones. En primer lugar, muchos l. Ttansrerencia de argumentos .
lenguajes de programacin de uso comn (como FORTRAN. .COBOL y michos len-
guajes de mquina) no permiten programas recursivos ..Problemas como el de Ias 2. Ubicacin e inicializacin de variables locales
Torres de Hanoi y la conversin de prefija a postfija, cuyas soluciones se pqeden deri'. 3. Transferencia de control a la funcin
var y establecer de manera muy simple mediante tcnicas no recursivas: se pueden
programar en esos lenguajes .simulando la solucin recursiva por medio de opera- COhiinliacirt se examinar cada uno de los pasos.
ciones ms elementales. Cuand.o se sabe que una solucin recursiva es correcta (y
por lo general es muy fcil comprobar que lo es) y se establecen tcnicas parn con- 1 .. transferencia de argumentos: Para un parmetro en e, se hace iocameh-
Vertir una solucin recursi_va _en una que no lo es, e_s _p9~ible_crer 1:m solucin t~na,copta del argu'."etito dentro de la funcin y todos los cambios al parmetros~
correcta en un lenguaje no recursivo. No es raro que tm programador sea capaz trnnsfteren . esa copia local. El efecto de este esquema es que el argumento or inai
de plantear una solucin recursiva a un prqblema. La hbilidal para generar una ?
~.;rie.t~~d~1 dedbedalteradrse. En est~, mtodo, se asigna memoria pata ei irgtim~nto
area e atos e la functon ..
.solucin no .recursiva de un algoritmo recursivo es indispe11sable cuando se usa un
compilador que no acepta la recursin.
Otro motivo para examinar la creacin de recursin es que permite entender
2 . Ubicaein
. .
.e ini.cializacin
.
de va. riablhs
e
1ocales Lueg o d. e pasar
. 1os argU-
,,,,- sus impHcaciones y algunas de sus trampas ocultas. Aunque ess trampas no existen ~~ntod, se ubican .las vatlables locales. Estas incluyen todas aquellas que son decla-
..
""'"'' en .las definiciones matemticas que emplean la recur~in, parecen ser un acompa- dtir:~ie\rnera ~r~cta, en la fundn, asi corno las provisionales (jue deban crearse
ante)nevitable de la creacin en un lenguaje y en una mquina reales:' curso e a eJecuc1on. Por ejemplo; al evaluar la expresin
Por. ltimo, incluso en un lenguaje como C que admite la recursin, una solu- x + Y + Z
dn recusiva es con frecuencia r_s c;:ostosa qu_e un~ que nio,es,._en trminos tanto
.de tiempo y' como de espacio. Dicho costo es, por lo general, un pequeo precio que
1\ij~~v~trJarSe ~t1a oc~ljdad de me.rnorf par gurdrtr el valr de X+
y de rrnera
hay que pagar por la simplicidad lgica y la auto-documentacin de la solucin re-
cursiva. 'Sin embargo, en un programa de produccin (como un compilador, por .. Har e ta10:Jf~~: a este. _se debe reservar otr~ .localidad de merndiia iafa guare
a expres1on una vez que ha Sido evaluada. Tales localidades se

J42 Estructuras de datos. en C


143
conocen como temporales, debido a que se necesitan slo de manera temporal programa principal procedimiento b procedimiento e procedimiento d
durante el curso de la ejecucin. De manera anloga, en una instruccin como Direccin de Direccin Direccin
regreso de regreso de regreso
llamada ad
x - fact(n)
debe reservarse una localidad temporal para guardar el valor defact(n) antes de que
llamada a b llamada a e
ste sea asignado ax.
3. Transferencia de control a la funcin. En este punto, no puede transferirse (a)
an el control a la func'in, puesto que no se han tomado las provisiones para salvar procedimiento b
Control
main program procedimiento e
la direccin de regreso. Cuando se le da el control a una funcin, sta tiene que
Direccin de Direccin de
regresar finalmente el control a la rutina de llamada por medio de "uri salto". Sin regreso regreso
embargo, no puede ejecutar dicho salto a menos que conozca la localidad a la que
llainada ad
debe regresar. Como esta localidad est dentro de la rutina de llamada y no en la
propia funcin, la nica manera en que la funcin puede conocer esa direccin es Control
cuando la ha pasado como argumento. Esto es exactamente lo que ocurre. Adems llamada a b llamada a e
de los argumentos especificados por el programador, tambin hay un conjunto de
argumentos implcitos que contienen la informacin necesaria para ejecutar la fun-
(b)
cin y regresarla de manera correcta. De esos argumentos implcitos, el ms impor-
tante es la direccin de regreso. La funcin almacena esta direccin en su propia Figura 3.4.1 Serie de procedimientos que se !laman entre s.
rea de datos. Cuando est lista para restablecer el control al programa de llamada,
la funcin recupera la direccin de regreso y "salta" hacia la localizacin indicada.
Una vez que se han pasado los argumentos y la direccin de regreso, es posible . Co'.o P~~o observarse, la cadena de direcciones de regresoforma una il .
decir, 1a.d1:ecc10n de regreso ms reciente que se agregar a la cadena ser la P. a, es
transferir el control a la funcin, debido a que ya se realiz todo lo requerido para
asegurar que la funcin opere sobre los datos apropiados y luego regresar a salvo a :~ ~:s~~:;~~~:;i~e~~eufalqu!:r punto, sl~ se puede accesar la direccin d~r~:~:~
uncion que se esta eJecutando en .
la rutina de llamada. represe.~ta el tope de la pila. Cuando se saca un elemento de 1:s~1:e:ento, la cual
~a fu?_c1on r~gresa), aparece un nuevo tope en la rutina de llamada. L~ ll~;~d~u::~~
Regreso desde una funcin
q~7~;~:~r~e;~:;~~fecto de poner un elemento dentro de la pila, y el regreso, el de
Cuando una funcin regresa el control, se ejecutan tres acciones. Primero, se
recupera la.direccin de retorno y se almacena en una localidad segura. Luego se li-
Ejecucin de funciones recursivas
bera el rea de datos de la funcin. Esta rea de datos contiene todas las variables
locales (incluyendo las copias locales de argumentos), las temporales y la direccin
de regreso. Por ltimo, efecta un "salto" hacia la direccin de regreso salvada con
anterioridad. Esto restablece el control a la rutina de llamada en el punto siguiente a
la instruccin que inici la llamada. Adems, si la funcin dio como resultado un va-
lor, ste se coloca en una localidad segura, desde la que puede recuperarlo el progra-
ma de llamada. Por lo general esta localidad es un registro de hardware reservado
para este propsito.
Supngase que el procedimiento principal llam a una funcin b, la que llam
;t a e, la que llam a d. Esto se ilustra en la figura 3.4.la, donde se indica que el
control reside en ese momento en alguna parte de d. Dentro de cada funcin, se se-
para una localidad para la direccin de regreso. As, el rea de direccin de regreso
de d contiene la direccin de la instruccin en e que sigue de inmediato a la _llamada a
d. La figura 3.4.lb muestra la situacin que sigue inmediatamente al regreso de d
a c. Se recupera la direccin de regreso dentro de d y se transfiere el control a dicha
direccin.
Estructuras de datos en C Recursin 145
144
Cmo puede definirse el rea de datos de esta funcin? Esta debe incluir el
una de las variables y parmetros locales. Se puede pensar en esas pilas como pilas parmetro n y las variables locales x y y. Como podr verse, no se necesitan tempo-
separadas, una para cada variable local. Otra posibilidad, ms cercana a la realidad, rales. El rea de datos tambin debe incluir una direccin de regreso. En este caso,
es pensar en todas ellas como una pila simple y enorme. Cada elemento de esta enor- hay dos puntos posibles a los que se podra regresar: a la asignacin defact(x) ay y
me pila es toda una rea de datos que contiene partes secundarias para representar al programa principal que llama afact. Supngase que se tienen dos etiquetas y que
las variables locales independientes o parmetros. la etiqueta abe/2 es para la parte del cdigo,
Cada vez que se llama a la rutina recursiva, se asigna una nueva rea de da.tos.
Los parmetros dentro de dicha rea se inicializan para asignar los v~lores de sus ar-
label2: y - result;
gumentos correspondientes. La direccin de regreso dentro de esta area de daws se
inicializa como la direccin de la instruccin que sigue a la de llamada. Cualqmer re- dentro del programa que se simula, y labe/1 la etiqueta de la instruccin
ferencia a variables locales o parmetros se efecta a travs del rea de datos
vigente. . . . label1: return(result);
Cuando la rutina recursiva regresa, se almacera el valor resultante (s1 lo hay) Y
se salva la direccin de regreso, se libera el rea de datos y se ejecuta un "salto" ha- No es ms que una convencin el hecho de que la variable result contenga el valor
cia ia direccin de regreso. La funcin de Iiamada recupera el valor al que se regresa que debe regresar con una llamada a la funcin fact. La direccin de regreso se al-
(si lo hay), termina ia ejecucin y asigna su propia rea de datos, ia que ahora est en macenar como un entern (que puede ser 1 o 2). Para efectuar un regreso desde una
el tope de la pila. . . llamada recursiva se ejecuta la instruccin:
Examnese ahora la forma en que pueden simularse las acciones de una fun-
cin recursiva. Para ello, es necesario que una pila para Jas reas de datos est switch(i) {
definida por case 1: gato label1;
Case 2: goto label2;
#define MAXSTACK 50; I* fin de case *I
struct stack {
ilt top; As, si = = 1, se ejecuta un regreso al programa principal que llam a facl, si
strutt dataarea itel[MAXSTAtKJ;
= = 2, se simula un retorno a la asignacin del valor resultante a la variable y en la
};
ejecucin previa de fact.
La pila del rea de datos para este ejemplo se puede definir como sigue:
Por si misma, daiaared es un estructura que contiene los diversos elementos
que existen en un rea de datos y se la debe definir para que contenga los campos re-
queridos de la funcin particular que se est simulando. #define MAXSTACK 50
struct dataarea {
int param;
Simulacin de factorial
int x;
Veamos un ejemplo especfico: ia funcin factdal. A continuacin se presen- long int y;
ta el prngrafua para esta funcin, el que incluye de maneta explcita las variables short int retaddr;
};
temporales y omite la prueba para las entradas negativas:
struct stack
int top;
fact(n) struct dataarea item[MAXSTACKJ;
int n; };
{
iri.t x, y;
El campo del rea de datos que contiene el parmetro simulado se llamaparam, y no
n,yara evitar confusiones con el parmetro n transferido a la funcin de simulacin.
if ( n == d)
Ta111bin se declara un .rea de datos vigente para guardr los valores de las variables
rturti(1);
en la lla.mad.a "vigente;' simulada a la funcin recursiva. La declaracin es:
X= h-li;
y = fact(x);
return( n y); struct dataarea currarea;
I * fin di, fct * I

Recursin 147
146 Estructurs de datos en e
Adems, se declara una variable simple result mediante: Al iniciar la simulacin, se debe inicializar el rea vigente de manera que curra-
rea.param sea igual a n y currarea.retaddr a 1 (para indicar un regreso a la rutina de
long int result; llamada). Asimismo, se debe insertar un rea de datos de relleno en la pila para que
no ocurra un subdesborde al ejecutar popsub en el retorno a la rutina principal. Esta
Esta variable se usa para comunicar al usuario el valor resultante defact de una lla- rea de datos ficticia debe inicializarse tambin para que no provoque un error en la
mada recursiva defact y defact a la funcin de llamada externa. Como los elemen- rutina push (ver la ltima oracin del prrafo anterior). As, la versin simulada de
tos de la pila de las reas de datos son estructuras y como en muchas versiones de C la rutina recursiva fact es la siguiente:
una funcin no puede dar como resultado una estructura, no se usa la funcin pop
para eliminar un rea de datos de la pila. En lugar de ello, se escribe una funcin struct dataarea
popsub definida por: int param;
int x;
long int y;
popsub(ps, parea)
short int retaddr;
struct stack *ps;
str-uct da ta a rea *parea; ) ;
struct stack
int top;
La llamadapopsub(&s, &area) elimina elementos de la pila y asigna rea al elemen-
struct dataarea item[MAXSTACKJ;
to eliminado. Los detalles se quedan. a manera de ejercicio. ) ;
Un regreso de fact se simula mediante el cdigo
simfact(n)
result = value to.be returned; . int n;
i = currarea.retaddr; {
popsub(&s, &currarea); struct dataarea currarea;
switch(i) { struct stack s;
case 1: goto label1; short i;
case 2: gota label2; long int result;
I * fin de switch * I s.top = -1;
I* inicializacin de un rea de datos simulada *I
Es posible simular una llamada recursiva a fact poniendo el rea de datos currarea.param o;
vigente en la pila, reinicializando las variables currarea.param y currarea.retaddr currarea.x o;
como el parmetro y la d, eccin de regreso de la llamada en cuestin respectiva- currarea. y O;
mente, y transfiriendo despus el control al principio de la rutina simulada. Hay que currarea.retaddr o;
recordar que currarea.x guarda el valor de n - I, el que ser el nuevo parmetro. /* colocar el re de datos simulada en el rea *I
Tambin debe recordarse que en una llamada recursiva se desea finalmente regresar push (&s, &currarea);
I* Asignar al parmtdro y a la direccin de retomo *!
a label 2. El cdigo para hacerlo seria: /* del rea de datoS actual sus valores apidp.dos *!
currarea.param = n;
push{&s, &currarea); currarea.retaddr = 1;
currarea.param = currarea.x; start: /* Este es el punto de inicio de la rutina *!
currarea.retaddr = 2; /* factorial simulada. *f
goto start; /" sta'rt es la etiqueta de inicio de * / if ( currarea. param == O) {
!* la rutina simulada . *; /* simulacin de return(l); *I
result = :L;
Por supuesto, las rutinas popsub y push deben escribirse de manera._que permi- i = currarea.retaddr;
tan eliminar y poner estructuras enteras de tipo dataarea en lugar de variables popsub(&s, &currarea);
switch(i) {
simples. Otra imposicin de la implantacin de pilas por medio de arreglos es que la
case 1: goto label:L;
variable currare.y debe inicializarse 'con algn valor, pues de IO contrario resultar di~e 2: goto label2;
un error en la rutina push al asignar currarea.y al campo correspondiente en el rea I * fin de switch * I
superior de datos cuando comienza el programa.

148 Estructuras de datos en C Recursin 149


} /* fin dei *I
currarea.x = currarea.param - L; el antiguo valor den debe usarse en la multiplicacin despus del regreso de la llama-
I* simulacin de la llamada recursiva a fact *! da recursiva afact. Sin embargo, no ocurre lo mismo con x y y. En realidad, el valor
push(&s, &currarea); de y no est definido an en el momento de la llamada recursiva, por lo que es evi-
currarea.param = currarea.x;
dente que no es necesario colocarlo en la pila. De manera anloga, aunque x si se
currarea.retaddr = 2;
gato start; define al hacer la llamada, no se vuelve a usar de nuevo despus de la misma: as
que, por qu razn hay que salvarlo?
1 abe 12 : /* Este es el punto al cual se regresa
I* de la llamada recursiva. Asignar a Este punto puede ilustrarse de manera ms precisa por medio del siguiente ra-
I* currarea.y el valor regresado. zonamiento. Si x y y no se declaran dentro de la funcin recursiva fact, sino como
currarea.y result; variables globales, la rutina funcionara bien, As, la a.ccin automtica de apila-
I* simulacin de return ( n * y) miento y desapilamiento ejecutada por la rectrsin par las variables locales x y y
result = currarea.~aram * currarea.y; resulta innecesaria.
i = currarea.retaddr; Otra pregunta interesante sera saber si se necesita realmente la direccin de
popsub(&s, &currarea); regreso en la pila. Como slo hay una llamada recursiva textual a fact, existe slo
switch(i) { una direccin de regreso dentro defact. La otra direc.cin de regreso es hacia la ruti-
case .1: gota label1; na principal que llam inicialmente afact. Pero, si se supone que al inicio de la simu-
case 2: gato label2;
lacin no se guard un rea de datos de relleno en la pila, entonces deber colocarse
/* fin de switch *I en la pila un rea de datos slo cuando se simula una llamada recursiva. Si se eli-
label1: / * En este punto se regresa a la rutina main.
return(result); minan elementos de la pila al regresar de una llamada recursiva, esta rea debe eliminar-
/* fin ae simfact */ se. Sin embargo, si se intenta sacar elementos de la pila durante la simulacin de un
regreso al procedimiento principal, ocurrir subdesborde. Mediante el uso de
Es necesario seguir la ejecucin del programa anterior para n 5 y tener la se- popandtest; en lugar de popsub, puede verificarse si ocurrir subdesborde; en caso po-
guridad de que se ha comprendido qu hace el programa y cmo lo hace. sitivo, hay que regresar en forma directa a la rutina exterior en lugar de hacerlo a
Advirtase que no se reserv espacio en el rea de datos para valores tempora- travs de una etiqueta local, lo que signfica que se puede eliminar una de las direc-
les, pues no es necesario almacenarlos para uso posterior. La localidad temporal que ciones de regreso. Como esto deja una sola direccin de regreso posible, es innecesa-
guarda el valor de n * y en la rutina recursiva original se simula mediante el valor rio que sta est en la pila.
temporal de currarea.param * currarea.y en la rutina de simulacin. En general, no De este modo, el rea de datos ha sido reducida para que contenga el par-
es ste el caso. Por ejemplo, si una funcin recursiva/une/ contiene una instruccin metro nico y la pila puede declararse como

x =a* funct(b) + e* funct(d); #define MAXSTACK so


struct stack {
para el valor t~mporal de a* funct(b) debe salvarse durante la llamada recursiva a 1nt top;
funct(d). Sin efoba,rgo, en el ejemplo de la funcin factorial no se requiere apilar los int param[MAXSTACK];
valores temporales. };

Perfeccionamiento de la.rutina simulada El rea de datos vigente se reduce a una variable simple declarada m~e:
int currparam;
El anlisis anterior conduce de manera natural a 'preguntarse si en verdad es
necesario que todas las variables locales estn en la pila. Se debe salvar una variable Ahora el programa es compacto y comprensible.
en la pila slo si su valor en el punto de inicio de una llamada recursiva debe usarse
otra vez despus del regreso de esta llamada. A continuacin se examina si las simfact(n)
variables n, x y y cumplen este requisito. Es evidente que n no debe apilarse. En la int n;
instruccin {
struct stack s
shott int und;
y n * fact(x); long int result, y;
int currparam, x;

150 Estructuras de datos en C


~ecursin 151
s.top = -1; son particularmente molestas porque interrumpen el flujo de pensamiento en el
currparam n momento en que podra llegarse a entender qu est pasando. Ahora se ver si es po-
start: !* Este es el punto de inicio de la *! sible transformar el programa en una versin ms legible.
I* rutina factorial simulada. *! Varas transformaciones se manifiestan de inmediato. En primer lugar, las ins-
if (currparam == O) { trucciones
!* simulacin de return (1) *!
popandtest(&s, &currparam, &und);
result = 1;
. switch ( und) {
popandtest(&s, &currparam, &und);
case FALSE: gato label2;
switch(und) {
case TRUE: gato label1;
case FALSE: gato label2;
case TRUE: gato label1;
/ * fin de switch * I
! * fin de switch * I
I* fin de i *I se repiten dos veces para los dos casos currparam = = O y currparam ! = O. Es fcil
I* currparam !=O*! hacer una combinacin con ambas secciones.
x = ~urt~ara- - L; Una observacin adicional es que a las dos. variables x y currparam se les asig-
/* simulacin de la llamada 'recursiva. a fa.et *I nan valores mutuos y qte nunca se usan de manera simultnea; de all que se las
push(&s, currpara); pueda combinar y referir como una sola variable x. Lo mismo es vlido para las
currparam = x.;. variables result y y, las que se pueden combinar y aludir como una sola variable y.
gato start: Luego de realizar esas transformaciones, se llega a la siguiente versin de sim-
label2: /* Este es el punto al que se regresa *I fact:
/* de la llamada recursiva. Asignar a *I
!* y el valor regresado *f
struct stack {
y= result; int top;
/* simulacin de returri. ( n * y ) ; *!
int param[MIXSTICK];
result = Currparam * y;
};
popandtest(&s, &turrparam, &und);
simfact(n)
switch(und) {
int n;
case TRUE: gato label1;
{
case FALSE: gato label2;
struct stack s;
! * fin de switCh * I short int una;
labeU: !* En _este punto se regresa a la rutina *!
principal. *I int x;
!*.
long int y;
return(result);
/ * fin de Simlact * I s.top = -1;
X= n;
s ta r t : /* Este es el inicio de 1~ rutina factorial *!
Eliminacin de gatos I* asocida *I
if ( X == 0)
Aunque este ltimo programa es ms simple que el anterior, an est lejos de y = 1
ser el ideal. Si se viera el programa sin observar su derivacin, sera muy difcil iden- else. {
tificar que su objetivo es calcular la funein factorial. Las instrueiones push(&s, x--);
gato start;
I* fin de else *!
goto start;
label1: popandtest(&s, &x, &und);
if (und == TRUE)
y .return (y) ;
label2: y*= x;
gato label2; gato label1;
} / * fin de simiact * I

152 Estructuras de datos en C Recursin . 153


Ahora se comienza a llegar a un programa legible. Advirtase que el programa Pero este programa es la implantacin directa en C de la versin iterativa de la fun-
consiste en dos ciclos: cin factorial tal como se present en la seccin 3.1. El nico cambio es que x vara
de l a n en lugar de hacerlo de na l.
1. El ciclo de toda la instruccin if, etiquetado con start. Este ciclo termina cuan-
do x es igual a O, en cuyo momento y se transforma en l y la ejecucin conti- Simulacin de las Torres de Hanoi
na en la etiqueta /abe/ l.
2. El ciclo que comienza en /abe/ l y termina con la instruccin goto /abe/ l. Este Y-a se demostr que las transformaciones sucesivas de una simulacin no recur-
ciclo termina una vez que se vaca la pila y ocurre el subdesborde, momento en siva de una rutina recursiva pueden conducir a un programa ms simple para resol-
ver un problema. Ahora se ver un ejemplo ms complejo de recursin, el problema
el que se ejecuta un regreso.
de las Torres de Hanoi, presentado en la seccin 3.3. Se simular la recursin del
Estos ciclos pueden sin dificultad, ser transformados en ciclos while explcitos problema y se intentar simplificar la simulacin para producir una solucin no
como sigue: recursiva. En seguida se presenta de nuevo el programa recursivo de la seccin 3.3.

I * iteiacin d resta *I towers(n, frompeg, topeg, auxpeg)


start: while ( x != D) int n;
push(&s; Xc-,-); char auxpeg, frompeg, topeg ;,
y= 1; 1
pppandtest(&s, &x, &und.); ! * Si es slo un disco, mover y regresar. *I
label1: while (und == FALSE) { if ( n == 1) {
y * = Xi printfe'/n%s%c%s%c%", "mover disco l del poste", frompeg,
popandtest (&s, &x, &und); "al poste", topeg);
!* findewhile * ! return;
return(y); !* fin de if *I
I* n-1 discos de arriba de A a B, usando
MoVf!.I los */
Examnese con ms detenimiento ambos ciclos. x se inicia con el valor del pa- * como auxiliar *I
rmetro de entrada 11 y se reduce en l cada vez que se repite el ciclo de sustraccin. towers(n-1, frompeg, auxpeg, topeg.);
I* Move remaning disk from. A to c. *I
Cada vez que se le asigna un nuevo valor ax, se salva el valor antiguo de x en la pila.
printfcn/n%s%d%s%c%s%c%",'~mover disco", l, 1'delposte 11 ,
Esto prosigue hasta que x es igual a O. As, despus de ejecutar el primer ciclo, la pila frompeg, "al poste", topeg)
contiene los enteros del l al n, de la parte superior a la inferior. I* Mover n-1 discos de B hacia C empleando a */
Lo nico que hace el ciclo de multiplicacin es extraer de la pila cada uno de es- I * A como auxiliar *I
tos valores y poner en y el resultado de la multiplicacin de dichos valores con el towers(n-1, auxpeg, topeg, frompg);
valor antiguo de y. Si ya se sabe lo que contiene la pila al principio del ciclo de mul- I * fin de fowers *I
tiplicacin qu caso tiene sacar sus elementos? Dichos valores pueden usarse en for-
ma directa. Se puede eliminar la pila y todo .el primer ciclo y remplazar el ciclo de Antes de proseguir, hay que asegurarse de que se ha entendido el problema y la solu-
multiplcacin por uno que multipHqu~ cad uno de los enteros del l al n por y, paso cin recursiva. En caso contrario, vulvase a leer la seccin'3.3.
a paso. El programa resultante es En esta funcin, hay cuatro parmetros, cada uno de los cuales est sujeto a
cambios en cada llamada recursiva. En consecuencia, el rea.de datos debe contener
simfact(n) elementos que representen a los cuatro. No hay variables locales. Hay un solo valor
int n temporal que se necesita para guardar el valor de. n - l, pero _sta se, puede repre-
1 sentar por un valor temporal similar en el programa de simulacin y no tiene que
int x; estar apilada. Hay tres puntos posibles a los que regresa la funcin en varias llama-
long int y; das: el programa de llamada y los dos puntos que siguen a las llamadas recursivas.
Por lo tanto, se nec~sitan cuatro etiquetas:
for (y=x=1; x <= n; x++)
y*= x; start:
return(y) label1:
J* fin de simact *I labe12:
label3:

Estructuras de datos en C Recursn 155


154
La direccin de regreso se codifica como un entero (l, 2 o 3) dentro de cada case 3: goto labe13;
rea de datos. I * fin de switch * /
Considrese la siguiente simulacin no recursiva de towers: I* fin dei *I
I* Esta es la primera llamada recursiva . *I
push(&s, &currarea);
struct dataarea { --currarea.nparam;
int nparam; temp = currarea.auxparam;
char fromparam; currarea.auxparam = currarea.toparam;
char toparam;
currarea.toparam temp;
char a_uxparam;
currarea.retaddr = 2;
short int retaddr; goto start;
) label2: I* Se regresa a este punto desde la primera *I
struct stack { I* llamada recursiva . *I
int top; printf(11/n.%s%d%s%c.%s%c", "mover disco",
struct dataarea item[MAXSTACKJ;
currarea.nparma, "del poste",
) ; currarea.fromparam,' 11al poste", currarea.toparam);
simtowers(n, frompeg, topeg, auxpeg)
!* Esta es la segunda llamada recursiva. *
push(&s, &currarea);
int n; --currarea.nparam;
char auxpeg, frompeg, topeg;
temp = currarea.fromparam;
{ currarea.fromparam = currarea.auxparam;
struct stack s;
currarea .. auxparam = temp;
struct d~ta~rea currarea;
currarea.retaddr = 3
char temp; gotq s.tart;
short int i;
lbel3: I* Se regresa este 'punto d~;de l Segunda *f
I* llamada recursiva. *I
s.top = -1; i = currarea.retaaar;
currarea.nparam = O;_ pop(&s, &currarea);
currare a. fromparam = 1 , ;
switch(i) {
currarea. tqparam = ' , ;
case 1: goto labe11;
currarea.auxparam = 1 1 ;
case 2: gato label2;
currareairetaddr = o;
/* Colocar en la pila un rea de datos simulados. *I case 3: goto label3;
I * fin de switch *I
push(&s, &currarea);
I* Asignar parmetros y direcciones de regreso *I label1: return;
} I* fin de simtowers *I
I* del rea de datos actual a sus valores apropiados. *I
currarea.nparam = n;
currarea.fromparam = frompeg; Al!ora se simplificar el. programa. En primer lugar, d.ebe observarse que se
?urrarea.toparam = topeg; us.an tres etiquetas para indicar direcciones de regreso: una para cada una de las dos
currarea.auxparam = auxpeg; llamadas recursivas y otra para el regreso al programa principal. Sin embargo, el
cuirarea.retaddr = 1; regreso al programa principal puede sealarse por un subdesborde en la pila, de la
start: /* Este es el inicio de la rutina simulada *I
misma.forma que en la segunda versin de simfact. Esto deja dos etiquetas de regre-
if (currarea.nparam ~= 1) {
printf("/n%s%c%s%c", \'mover disco 1 del poste", so. Si pudiera eliminarse otra ms, sera innecesario guardar en la pila la direccin
currarea.fromaram, ' 1al post~", currarea.toparam ' ) ; de regreso, ya que slo restara un punto al que se podra transfrir el control si se
i cu-rrarea. retaddr; e.liminan .los elementos de la pila con xito. Ahora dirijamos nuestra atencin .a la
pop(&s, &currarea); segunda llamada recursiva y a la instruccin:
switch(i) {
case 1: goto label1; towers(n-1, auxpeg, topeg,. fr6~peg);
case 2: goto label2;

Estructuras de datos en C 157


156
del rea de datos vigente, ':s necesari? declarar una variable adicional, temp, de ma-
Las acciones que ocurren en la simulacin de esta llamada son las siguientes: nera que los valores sean mtercamb1ables.
A continuacin se presenta una revisin de la no recursiva de towers:
l. Se coloca el rea de datos vigente a 1 dentro de la pila.
2. En la nueva rea de datos vigente a2, se asignan los valores respectivos n - 1, struct dataarea {
auxpeg, topeg y frompeg a los parmetros. int nparam;
3. En el rea de datos vigente a2,. se fija la etiqueta de retorno a la direccin de la char frompatam;
instruccin que sigue de inmediato a la llamada. char toparam;
char auxparam;
4. Se salta hacia el principio de la rutina simulada. };
struct stack {
Despus de completar la rutina simulada, sta queda lista para regresar. Las si- int top;
guientes acciones se llevan a efecto: struct dataarea item[MAXSTACKJ;
};
S. Se salva la etiqueta de regreso, /, del rea de datos vigente a2. simtowers(n, frompeg , t opeg, auxpeg)
int n;
6. Se eliminan de la pila y se fija el rea de datos vigente como el rea de datos
eliminada de la pila, a l. char a~xpeg, frompeg, topeg;
struct stack s;
7. Se transfiere el control a/.
struct dataarea currarea;
short int und;
Sin embargo, / es la etiqueta del final del bloque del programa ya que la segun- char temp;
da llamada a towers aparece en la ltima instruccin de la funcin. Por lo tanto, el
siguiente paso es volver a eliminar elementos de la pila y regresar. No se volver a s.top - -1;
hacer uso de la informacin del rea de datos vigente a 1, ya que sta es destruida en currar~a.nparam = n;
la eliminacin de los elementos en la pila tan ,pronto como se vuelve a almacenar. curra~ea.fromparam . ~ fromp~g;
Puesto que no hay razn para volver a usar esta rea de datos, tampoco hay razn currarea.toparam = topeg;
para salvarla en la pila durante la simulacin de la llamada. Los datos se deben sal- currarea.auxparam = auxpeg
start: I* Este es el inicio de la.rutina simulada;
var en la pila slo si se van a usar otra vez. En consecuencia, la segunda llamada a *!
towers puede simularse en forma simple mediante: if (currarea. nparam ,,,-1) {
printf("/n%s%c%s%c%", "mover disco l del.poste",
l. El cambio de los parmetros en el rea de datos vigente a sus valores respecti- currarea.frompeg, 11 al poste", currarea.toparam)
I* simular el regreso '
vos. popandtest(&s, &currarea, &und);
*!
2. El "salto" al principiode la rutina simulada. if (und -- TRUE)
return;
Cuando la rutina simulada regresa puede hacerlo en forma directa a la rutina goto retaddr;
que llam a la versin vigente. No hay razn para ejecutar un regreso a la versin vi- I* fin deif *!
gente, slo para regresar de inmediato a la versin previa. Por lo tanto, se elimina la I* simular la primera llamada recursiva
*I
necesidad de guardar en la pila la direccin de regreso al simutar la llamada externa push(&s, &currarea);
--currarea.nparam;
(ya que se puede sealar mediante subdesborde y simular la segunda llamada recur-
temp -~ eurcarea.toparam;
siva (ya que no hay necesidad de salvar y Volver a almacenar' el rea de datos de la
currarea.toparam = currarea.auxparam;
rutina de llamada en este momento). La nica direccin de regreso que resta es la currarea.au~~aram = .temF;
que sigue a la primera llamada recursiva; goto start;
. ' retaddr: I* Pua . .
Ya que slo queda una direccin de regreso posible, no tiene caso guardarla en n.to e regreso desde la primera
!* *!
la pila para que se vuelva a insertar y eliminar con el resto de los datos. Siempre qu llamada recursiva *!
se eliminan elementos de la pila con xito, hy una sola direccin hacia la que se printfC'/n%s%.d%s%c%s%c", "mover disco",
puede ejecutar un "salto": la instruccin que sigue a la primera llamada. Si se detec- currarea.nparam, "del poste", c.urra,rea. fromparam,
ta subdesborde, la rutina regresa a la funcin de.llamada. Como los nuevos valores ''alposte", currarea.toparam)1
I* simulacin de la segunda llamada recursiva *
de las variables del rea de datos vigente se obtendrn a partir de los datos antiguos

:Rf;!qursin
158 Estructuras de datos en,C 159
.

.
--currarea.nparam; 3.4,5. Muestre que cualquier solucin del problema de las Torres de Hanoi que use un nme-
1 temp = currarea.fromparam; ro mnimo de movimientos debe satisfacer las condiciones que se enumeran en seguida.
1.
currarea.fromparam = currarea.auxparam; Use estos hechos para desarrollar un algoritmo iterativo directo para las Trres de
currarea.auxparam = temp; Hanoi. Implante el algoritmo .como un programa en C.
gota start; a. El primer movimiento implica el movimiento del disco ms pequeo.
/* fin de simtowers *I b. Una solucin que use los movimientos mnimos consiste en mover de manera al-
terna el disco ms pequeo y uno mayor que ste.
Al examinar la estructura del programa, se observa que ste puede reorganizar- c. En cualquier punto, slo hay un posible movimiento que implique un disco que no
se con facilidad en un formato ms simpe. Para ello, se comienza desde la etiqueta es el ms pequeo.
d. Defina la direccin cclica dejrompeg a topeg a auxpeg afrompeg en el sentido de
start.
las manecillas del reloj y en la direccin opuesta (de frompeg a auxpeg a topeg a
jrompeg). Supngase que una solucin con movimientos mnimos mueve siempre
while (TRUE) { el disco ms pequeo en una direccin a fin de mover una torre_dek-discos defrom-
while (currarea.nparam !- 1) { peg a topeg. Demuestre que una solucin con movimientos mnimos para mover
push(&s, &currarea); una torre de (k + !)-discos defrompeg a topeg movera siempre el disco ms peque-
--currarea.nparam; o en la direccin contraria. Como la solucin para un disco meve el disco ms
temp = currarea.toparam; pequeo en el sen'tido de las manecillas del reloj (un movimiento simple defrompeg
currarea.toparam -= currarea.auxPram; a topeg). esto sig,.nifica que el disco ms pequeo se mueve siempre en el sentido de
currarea.auxparam = temp; las manecillas de reloj cuando el nmero de discos es impar, y en la dire:cin con-
I*fin de while *I traria cuando el nmero es par.
printf( 11/n%s%c%s%c 11 , mover 1 del poste 11 ; e. La solucin se completa una vez que todos los disc0'-i quedan en un solo poste.
cu rrarea. fromparam, "al poste", currarea.toparam);
3.4.6~ Convierta el siguiente programa con esquema recur,:vo en una versin iterativa que no
popandtest{&s, &currarea, &und);
use pila.f(n) es una funcin que regresa TRUE o FALSE de acuerdo con el valor den,
if (und -- TRUE) y g(n) regresa un valor del mismo tipo que n (sin modificar n).
return;
printf( 11/n%s%d%s%c%s%c'\ "mover disco", currarea.nparam,
"del poste11 , urrarea.fromparam, "al poste'-\ rec(n)
currarea.toparam) int n;
--currarea.nparam; {
temp = currarea.fromparam; if (f(n) -- FALSE) {
currarea.fromparam = currarea.auxparam; I* Cualquier grupo de instrucciones en C *I
currarea.auxparam = temp; I* que no modifiquen el valor de n *I
!* fin de while *! rec(g(n));
!* fin de f *!
I* fin derec *I
Hay que seguir la accin de esta problema y ver como refleja la accin de la versin
recursiva original.
Generalizar el resultado al caso en que rec regresa un valor.
3.4.7. Seaf(n) una funcin, y seai! g(n) y h(n) funciones que regresan un valor del mismo
tipo que n sin modificarlo. Represente con (stmts) cualquier grupo de instn).cciones en
EJERCICIOS
C que no modifique el valor li, n. krnuestre que el esquema de programa recursivo rec
es equival_ente al esquema i:,., \'O iter:
3.4.1. Escriba una simulacin no recursiva de las funciones convett y find presentadas en la
seccin 3.3.
rec(n)
3.4.2. Escriba una simulacin no recursiva del procedimiento recursivo para la bsqueda bi-
int n;
naria y transfrmela en un rocedimiento iterativo.
{
3.4.3. Escriba una simulacin no recursiva defib. Es posible transformar sta en un mtodo if (f(n) -- FALSE)
iterativo? (stmts);
3.4.4. Escriba simulaciones no recursivas de las rutinas recursivas de las secciones 3.2 y 3.3 y rec(g(n));
de los ejen;:icios de dichas secciones. rec(h(n));
I* fin def *I

160 Estructuras de datos en C Recursin 161


} /* fin de rec *f lar en el caso del problema de convertir de prefija a postfija, donde la solucin
recursiva se desprende de manera directa de las definiciones. Una solucin no recur-
struct stack siva que requiera de pilas es ms difcil de desarrollar y est sujeta a ms errores.
int top; Por lo tanto, existe un conflicto entre la eficacia de la mquina y la eficiencia
int nvalues(MAXSTACK]; del programador. Con el constante crecimiento de los costos de programacin y la
}; disminucin de los costos de computacin se ha llegado al punto en el que, en la
. mayora de los casos, carece de valor el tiempo que emplea un programador para
iter(n)
construir laboriosamente una solucin no recursiva de un problema que puede resol-
int n;
verse de manera ms natural por medio de la recursin. Por supuesto que un progra-
struct stack s; mador incompetente, pero sumamente diestro, puede llegar a una solucin recursiva
complicada para un problema simple que se puede resolver de manera directa me-
s. top. = .-J. . ; diante mtodos no recursivos. (Ejemplo de esto es la funcin factorial e incluso la
push(&s, n); bsqueda binaria.) Sin embargo, cuando un programador competente identifica la
while(e~pty(&s) ==FALSE solucin recursiva como la ms simple para resolver un problema, lo ms probable
n = pop(&s); que es no valga la pena invertir tiempo y esfuerzo para descubrir un mtodo ms
i f (f(n)==FALSE) eficaz.
( stmts); No obstante, esto no siempre es as. Cuando se va a ejecutar un programa con
push(&s, h(n));
mucha frecuencia (a menudo hay mquinas completas dedicadas a correr conti-
push(&s, g(n));
nuamente el mismo programa), de modo que la eficacia creciente en la velocidad de
I* fin deif *f
! * fin de while *
!
ejecucin t,aga crecer significativamente el rendimiento efectivo total, entonces
I* fin de iter *I
valdr la pena invertir tiempo extra en la programacin. An en tales casos, quiz
sea mejor crear una versin no recursiva mediante la simulacin y la transformacin
Demuestre que las instrucciones if en iter pueden ser remplazadas por el siguiente ciclo: de la solucin recursiva que tratar de crear sta a partir del propio planteamiento del
problema.
while (f(n) == FALSE) Para hacer esto con ms eficiencia, es necesario escribir primero la rutina re-
(stmts) cursiva y despus su versin simulada, incluyendo todas las pilas y las temporales.
push(&s, h(n)); Una vez que se ha hecho esto, hay que eliminar todas las pilas y las variables
n=g(n); superfluas. La versin final es una depuracin del programa original y es, desde.
/* fin de while * ! luego, ms eficaz. Es evidente que la eliminacin de las operaciones superfluas re-
dundantes perfeccionan la eficacia del programa resultante. Sin embargo, toda
transformacin aplicada a un programa es ura nueva abertura por la que se puede
3.5. EFICIENCIA DE LA RECURSION escurrir un error.
Cuando es imposible eliminar una pila de la versin no recursiva de un progra-
En general, una versin no recursiva de un programa se ejecutar con mayor eficacia ma y cuando la versin recursiva no contiene parmetros o variables locales adi-
en cuanto a tiempo y espacio que una recursiva. Esto ocurre porque en la versin no cionales, la versin recursiva puede ser tan rpida o ms rpida que la no recursiva
recursiva se evita la sobrecarga debida a la entrada y salida de un bloque. Como ya con un buen compilador. Las Torres de Hanoi son ejemplo de un programa recursi-
se Observ, a menudo es posible identificar un buen numero de variables locales Y vo de este tipo. El factorial, cuya versin no recursiva no necesita pila, y el clculo
temporales que no se deben salvar ni volver a almacenar por medio de Una pila. Con de los nmeros de Fibonacci, que contiene una segunda llamada recursiva innecesa-
un programa no recursivo se puede eliminar esta actividad innecesaria. Sin embargo, ria (y tampoco necesita pila en la versin no recursiva), son ejemplos donde debe
en un procedimiento recursivo, el compilador es incapaz de identificar dichas evitarse la recursin en una implantacin prctica. En la seccin 5.2 se examinar
variables y, en consecuencia, stas son puestas y eliminadas de la pila para asegurar otro ejemplo de recursin eficaz (recorrido en orden de un rbol).
que no ocurre ningn problema. Otra cuestin que hay que recordar es que las llamadas explcitas a pop, push y
Sin embargo, tambin se ha visto que en ocasiones, la solucin recursiva es la empty, as como las verificaciones de desborde y subdesborde son bastante costosas.
va ms lgica y natural de resolver un problema. Es difcil que un programador De hecho, a menudo pueden sobrepasar el costo de la sobrecarga ocasionada por la
pueda desarrollar la versin no recursiva de la solucin al problema de las Torres de recutsin. Por ello, para aumentar al mximo la eficacia real en tiempo de ejecucin
Hanoi a partir del planteamiento del problema. Se puede hacer un comentario simi- de una versin no recursiva, se deben remplazar estas llamadas por cdigo en lnea y

162 Estructuras de datos en C 163


se deben eliminar las verifinciones de desborde y subdesborde cuando se sepa que se
trabaja dentro de los lmites del arreglo. .. .,
Las ideas y las transformaciones desarrolla_das en la prese~tacion de la funci~n
factorial y en el problema de las Torres de Hanoi se pueden aplicar a problemas mas
complejos cuya solucin no recursiva ? es tan aparente. El grado hast~. el que se
puede transformar una solucin recursiva (real ? simulada) en una solucion .directa
depende del problema en particular y del mgemo del programador.
Colas y listas
EJERCICIOS

3.5.1. Corra la versin recursiva y la no recursiva de la funcin factorial de las secciones 3,_..2 Y
3 .4, y examine la cantidad de tiempo y espacio requerido cuando n aumenta de tama~o.
3.5.2, Haga lo mismo que en el ejercicio 3.5.I. para el problema de las Torres de Han01.

En este captulo se presentan la cola y la cola de prioridad, dos importantes estruc-


turas de datos que se usan a menudo para simular situaciones del mundo real. Los
conceptos de pila y cola se prolongan despus a una nueva estructura: la lista. Asi-
mismo, se examinan varias formas de listas y sus operaciones asociadas y se presen-
tan varias aplicaciones.

LA COLA Y SU REPRESENTACION SECUENCIAL

La cola es una coleccin ordenada de elementos de la que se pueden borrar elemen-


tos en un extremo (llamado el frente de la cola) o insertarlos en el otro (llamado el
final de la cola).
La figura 4.1. la ilustra una cola que conti e.ne los elementos A, By C. A est en
el frente de la cola y C en el final. En la figura 4. l. 1b se borr un elemento de la
cola. Como los elementos slo se pueden bm:rar desde el frente de. la cola al eliminar A,
B pasa a ser el nuevo frente. En la figura 4.1.1 c, cuando se insertaron los elementos
D y E, fue necesario insertarlos desde el final de la cola.
Como D se insert en la cola antes que E, ser eliminada primero .. El primer
elemento insertado en una cola es el primero en ser eliminado. Por esta razn algu-
nas veces las colas son denominadas como listas fifo (first-in, first-out [primero en
entrar, primero en salir]) en oposicin a la pila que es una lista lifo (last-in, first-out
[ltimo en entrar, primero en salir]). En el .mundo real abundan ejemplos de cola.
Ejemplos comunes de cola son la fila en el banco o en una parada de camiones, o un
grupo de automviles esperando en una caseta de peaje.
Hay tres operaciones rudimentarias que se pueden aplicar a una cola, La
.operacin insert(q, x) inserta un elemento en el final de la cola q. La operacin
x ~ remove(q) borra un elemento del frente de la cola q y asigna su valor ax. La ter-
cera operacin, emp/y(q), dar como resultado false o true si la cola tiene o no
elementos.
164 Estructuras de datos .en C
165
abstract eltype remove(q)
rente QUEUE(eltype) q;
\ precondition empty( q) == FALSE;
postcondition remove == first(g');
q =""
sub(q', 1, len(g') - 1) ;
\
final abstract insert(g, elt)
( a)
QUEUE( el type) q;
frel)le eltype elt;
\ postcondition g == g 1 + <elt>;

e 1 Implantacin de colas en C
\
final Cmo debe representarse una cola en C? Una posibilidad es usar un arreglo
(b) para guardar los elementos.de la cola y usar dos variables,/ron/ y rear, para guardar
frente las posiciones en el arreglo del ltimo y el primer elementos de la cola dentro del
\ arreglo. Es posible declarar. una cola de enters q mediante,
B e D
E 1 #define MAXQUEUE 100
s.truct queue {
\
final 1nt 1tems[MAXQUEUEJ;
Figura 4.1.1- Una.cola..
int front, rear
l q;
Es posible obtener la cola de la figura 4. L 1 mediante la siguiente secuencia de opera,
dones, Supngase que la cola est vacia al inicio.
Por supuesto que el uso de unarreglo para guardar una cola introduce la posi-
insert(q, A);
bilidad de desborde si la cola rebasa el tamao del arreglo. Si se hace a un lado por el
insert( q, B); momento la posibilidad de .1esborde y subdesborde, puede implantarse la operacin
insert(q, C); (Figure 4.1.La) insert(q, x) mediante las instrucciones:
x = remo ve( q); (Figure 4,1,Lb; x is set to A)
insert(q, D); ~citems [++q.rearl ~ 1;
insert(q, E); (Figuie 4.1.Lc)

a
La operacin insert puede ejecutarse siempre debido qu': .no hay Umit~ par~
y la bpefacin x = remove(q) por medio de
el n111ero de elementos que puede c.ontener una co.la, La.oper.ac,on remov;;/m/":; = q.items [q.front++J.;
bar O slo se puede aplicar en caso de que.la cola no este vac1a; no es ~os, e ': ,m,
na/u~ elemento de una cola que no contiene ninguno. El resuhado del intento ile&al Al comienzo, q.rear es igual a -1 y q.front es igual a O. La cola est vaca
de eliminar un elemento de una cola vaca se conoce como subdesborde. Desde luego siempre que q.rear < q.jront. El nmero de elementos de la cola en cualquier
que la operacin emp1,v se puede aplicar siempre. ,.momento es igual al valor de q.rear - q.front + l.
Examnese qu ocurrira bajo esta representacin. La figura 4.1.2 muestra un
La cola como un tipo de datos abstracto arreglo de cinco elementos usado para representar una cola (es decir, MAXQUEUE
de una cola como mi tipo de datos abstracto es.directa. eltype es igual a 5). Al inicio, la cola est vaca (figura 4. l.2a). En la figura 4. l.2b se inser-
La representac,on . .. . t' de cola faron los elementos A, By C. En la figura 4. l.2c se eliminaron dos elementos y en la
se usa para denotar el tipo de elementos de la cola Ypara parametnzar e1 ipo . .;
4'1 .2d se insertaron dos nuevos elementos D y E. El valor de q.front es 2 y et de
abstract typedef <<el type>> QUEUE( eltype); J/,rear 4, por lo que s.lo hay 4 - 2 + 1 = 3 elementos en la cola. Como el arreglo
contiene cinco elementos, debe haber espacio para .que la cola se expanda sin riesgo
abstract empty(q) &;desbOrde.
QUEUE(eltype) q;
postcondltlon empty {len(q) =~ O);

Estructuras de datos en . 167


166
4

3
q. items
4

3
q. items

q. rear = 2
minar un elemento de una cola entraa de ma
elemento: aquel que est en ese mo
.. d b
rac1on e e reflejar este hecho Y no i r
o ..
t
. .

. ;
n mas e 1caz).
.
nera log1ca la manipulacin de un solo
men o en el frente La im l t ..
Pan ac10n de esta ope-
(ver el ejercicio 4. l .3 para una opc1omp i~ar fun smnumero de operaciones extraas
l
2
2 e tra soluc10n consiste en observar el arre l
B crculo y no como una lnea recta E d . g o que guarda la cola como un
s ec1r se puede
q. front = to del. arreglo (esto es, el elemento en la , o. . . imagmar q~e_el primer elemen-
o q. front = o A O

(a)
q. rear = -1
O

(b) cuando ste est vaco.


t
arreglo. Esto implica que aun cuand l ~l s1c10n O) sigue al ultimo elemento del
insertar un nuevo valor detrs de l o e t1?'0 elemento est ocupado, se puede
en e primer elemento del arreglo, siempre y
.. A continuacin se ver un ejemplo, Su n a . . .
q. items
q. items mentos en las posiciones 2 y d P g se ~ue una cola contiene tres ele-
3
. , , 4 e un arreglo de cmco elementos. Esta es la si-
4 4 E q. rear =4
3 D q. items
3 q. items
q. front = q. rear = 2 2 e q. front = 2
2 e 4
' E q. rear =4 4 E
3 D 3 D
o o 2 e q. front =2 2 e q. front = 2

(d)
(e)
o o F q. rear =O
Figura 4.1.2
(a) (b)
Sin embargo, para insertar F en la cola, q.rear debe incrementarse de I a 5 y
q.items[5] debe colocarse al valor de F; Pero q.items es un arreglo de 5 elementos/ q. items q. items
por lo que no es posible hacer la insercin. Se puede caer en la absurda situacin de 4 E q. front =4
que la cola est vaca y que, aun as, se pueda insertar en ella un nuevo elemento
4 E Q, front =4 )l

(vase si se puede llegar a esta situacin a travs de una secuencia de inserciones y eli-
3 3 .
,.
11

minaciones). Desde luego que la representacin por medio de arreglos trazada ante- 2 2
riormente es inaceptable.
Una solucin es modificar la operacin remove de tal manera que cuando se G q. rear = l
elimine un elemento toda la cola se recorra al principio del arreglo. La operacin
o F q. rear = O o F
x = remove(q) sera modificada entonces (y se hara a. un lado una vez ms la posibi-
(e)
(d)
lidad de subdesborde) a
J'
q. items 1

x = q.items[D];
4
for (i = O; i < q.rear; i++)
q.items(i] = .q.items[i+LJ; 3
q.rear--;
2

La cola ya no necesitar un campo front, debid0 a que el elemento en la posicin O G q. rear =1


del arreglo est siempre al frente de la misma. La cola vaca se representa por aquella o F q. front = O
en que rear es igual a -l..
Este mtodo, sin embargo, es poco eficaz. Cada eliminacin implica la transfe,. (e)
renda de todos los elementos restantes de la cola. Si una cola contiene 500 o 1000
Figura 4.1.3
elementos, esto significa el pago de un precio muy alto. Adems, la operacin de eli-

Estructuras de datos en O
168 169
tuacin de la figura 4. l.2d, la que se repite en la figura 4. l.3a. Aunque el arreglo no La operacin remove(q) puede codificarse como
est lleno, su ltimo elemento est ocupado. Si se insertara ahora el elemento Fen l
cola, ste puede colocarse en la posicin O del arreglo como se muestra en la figura remove(pq)
4. l.3b. El primer elemento de la cola est en q. items[2], el que es seguido por struct queue *pq;
q.items[3], q.items[4] y q.items[O]. Las figuras 4. l.3c, d y e muestran el estado de la {
cola cuando eliminan en primer lugar los elementos D y C, luego se inserta G y por if (empty(pq)) {
ltimo se borra E. printf("subdesborde en la cola");
Desafortunadamente, no es fcil determinar con esta representacin si la cola exit(l);
) I* fin deil *I
est vaca. La condicin q.rear < q.front deja de ser vlida para la verificacin de
vacuidad de la cola. Las figuras 4. l .3b, e y d, por ejemplo, ilustran situaciones en las if (pq->front == MAXQUEUE-1)
pq->front = o;
que la condicin es verdadera aunque la pila no. est vaca.
else
Una manera de resolver este problema es establecer la regla convencional de (pq->front)++;
que el valor de q.jront es el ndice del arreglo que precede de inmediato al primer ele- return (pq->items[pq->frontl);
mento de la cola en lugar del ndice del primer elemento mismo. Por lo tanto, como I* fin de remove *I'
q.rear es el ndice del ltimo elemento de la cola, la condicin q.front = = q.rear
implica que la cola est vaca. En consecuencia, una cola de enteros puede declararse Advirtase que pq es ya un apuntador para una estructura de tipo queue (cola), por
e inicializarse por medio de lo que el operador"&" no se usa para llamar a empty dentro de remove. Advirtase
tambin que se debe actualizar pq- > front antes de extraer un elemento .
. Desde luego, una condicin de subdesborde es significativa por lo general y sir-
#define MAXQUEUE 100 ve como seal para una nueva fase del proceso. Es probable que se desee usar una
struct queue { funcin remvandtest, cuyo encabezamiento eS
int items[MAXQUEUE];
int front, rear;
) ; remtandtest(pq, p2, pund)
struct queue q struct que u e_ *p,q;
q.front = q.rear = MAXQUEUE-1; int *px, *pund;

Advirtase que q.front y q.rectr son inicializadas como el ltimo ndice del Si la cofa no est vaca, esta rutina asigna *pund a FALSE y *px al elemento elimina-
arreglo y no como -1 o O, porque el ltimo elemento del arreglo precede de inme- do de la cola. Si la cola est vaca para que ocurra un subdesborde, la rutina asigna a
diato al primero dentro de la cola de esta representacin. Debido a que q.rear es *pund el valor TRUE. El cdigo de la rutina se queda como ejercicio para el lector.
igual a q.front; al inicio la cola est vaca.
La funcin empty puede codificarse como La operacin insert

empty(pq) La operacin insert comprende una verificacin de desborde que ocurre cuan-
struct queue *pq; do todo el arreglo est ocupado por elementos de la cola y se intenta insertar otro.
{ Por ejemplo considere la cola de la figura 4. 1.4a. Hay tres elementos en la .cola: C, D
return ((pq->front == pq->rear) ? TRUE FALSE); y E en q.items[2], q.items[3] y q.items[4], respectivamente. Como el ltimo elemen-
! * fin de e:rp.pty *I to de la cola ocupa el lugar q. items[4], q.rear es igual a 4, y q.front es igual a 1 debi-
do a que el primer elemento de la cola est en q.items[2]. En las figuras 4. l.4b y c se
Una vez que esta funcin existe, la prueba de vacuidad para una cola se puede inseitan en la cola los elementos Fy G. En este momento el arreglo est lleno y cual-
implantar con la instruccin quier intento por insertar otro elemento causara desborde. Pero esto est indicado
por el hecho de que q.front es igual a q.rear, lo que constituye precisamente la indi-
if (empty(&q)) cacin de subdesborde. Bajo esta implantacin parece que no hay manera de distin-
I* la cola est vaca */ guir entre la cola vaca y la que est llena. Es evidente que una situacin como sta
else
no resulta satisfactoria.
! * la cola no est vaca */ Una solucin es sacrificar un elemento del arrglo y permitir que la cola pueda
crecer nicamente hasta alcanzar el tamao del arreglo menos uno. As, cuando un

170 Estructuras de datos en 9 Colas y listas 171


q. items q. items La cola de prioridad

4 E q. rear =4 4 E Tanto la pila como la cola son estructuras de datos cuyos elementos estn orde-
3 D 3 D nados con base en la secuencia en que se insertaron. La operacin pop recupera el l-
e timo elemento insertado, en tanto que la operacin remo ve toma el primer elemento
2 2 e que se insert. Si hay un orden intrnseco entre los elementos (por ejemplo, el orden
q. front = l . q. front = l
alfabtico o numrico), las operaciones de la pila o la cola lo ignoran .
o o F q. rear =O La cola de prioridad es una estructura de datos en la que el ordenamiento
intrnseco de los elementos determina los resultados de sus operaciones bsicas. Hay
(a) (b) dos tipos de cola de prioridad: la de prioridad ascendente y la de prioridad descen-
q. items dente. La cola de prioridad ascendente es una coleccin de elementos en la que
4 E
pueden insertars~ elementos de manera arbitraria y de la que puede eliminarse slo el
elemento menor. Si apq es una cola de prioridad ascendente, la operacin
3 D pqinsert(apq, x) inserta el elemento x dentro de apq y la operacin
2 e pqmindelete(apq) elimina el elemento mnimo de apq y regresa su valor.
G q. front = q. rear = 1 La cola de prioridad descendente es similar, pero slo permite la eliminacin
del elemento mayor. Las operaciones aplicables a una cola de. este tipo, dpq, son
o F pqinsert(dpq, x) y pqmaxdelete(dpq). pqinsert(dpq, x) inserta el elemento x en dpq
(e) y es idntica desde un punto de vista lgico a pqinsert en el caso de una cola de
prioridad ascendente. pqmaxdelete(dpq) elimina el elemento mximo de dpq y
Figura 4.1.4 regresa su valor.
La operacin empty(pq) se aplica a ambos tipos de cola de prioridad y de-
termina. si la cola de prioridad est vaca. pqminde/ete o pqmaxdelete slo pueden
arreglo de 100 elementos se declara como una cola, sta puede tener hasta 99 elemen-
aplicarse a colas de prioridad ocupadas (es decir, cuando empty(pq) es FALSE).
tos. Si se intentara insertar el centsimo elemento, ocurrira un desbordamiento. Por
Una vez que se aplica pqmindelete para recuperar el elemento ms pequeo de
lo tanto, la rutina insert puede escribirse como sigue:
una cola de prioridad ascendente, se puede aplicar de nuevo para recuperar el si-
guiente elemento ms pequeo, y as sucesivamente. Por esto, la operacin recupera,
i.nsert(pq, x)
struct queue *pq; de manera sucesiva, elementos de una cola de prioridad en orden ascendente. (Sin
int- x; embargo, cuando se inserta un elemento pequeo despus de varias eliminaciones, la
{ siguiente recuperacin traer ese pequeo elemento, que puede ser ms pequeo que
/* Preparar el espacio para el nuevo elemento */ alguno previamente recuperado). De manera similar, pqmaxdelete recupera elementos
if (pq->rear == MAXQUEUE-1) de. una cola de prioridad descendente en el mismo orden. Esto explica la designacin
pq->rear = o; de una cola de prioridad e.orno ascendente o descendente.
else Los elementos de una cola de prioridad no necesitan ser nmeros o caracteres
(pq->rearl++; que se comparen de manera directa. Pueden ser estructuras complejas que estn orde-
/* se verifica si hay desboI'de * / nadas en uno o ms campos. Por ejemplo, las listas del directorio telefnico, que con-
if (pq '- >rear = = pq - >front} sisten en apellidos, nombres, direcciones y nmeros telefnicos, estn ordenadas por
prntf("desborde en la cola");
exit(l}; atelidos. /
I* fin de if *I 1 A veces/el campo con el que se ordenan los elementos de una cola de prioridad ni

PCf - > items[pq- > rear] = x si uiera es parte de los propios elementos; puede ser un valor externo especial, usado
return c n el objetivo especfico de ordenar la cola de prioridad. Por ejemplo, una pila puede
I * fin de insert * I v~se corno\una cola de prioridad descendente cuyos elementos estn ordenados por el
tie\tlPO de insercin. El ltimo elemento insertado tiene el mayor valor de tiempo de
i~sercin y es. el nico que puede recuperarse. De manera anloga, una cola puede ver-
La verificacin de desborde o de insert ocurre despus de ajustar pq- > rear,
se ci\'.11 una cpla de prioridad ascendente, cuyos elementos estn ordenados de acuer-
mientras que en remo ve la verificacin de subdesborde ocurre de inmediato al entrar
la rutina, antes de que se actualice pq- > front.

172 Estructuras de datos en C 173


do con el tiempo de insercin. En ambos casos, el tiempo de insercin no es parte ro, el proceso de bsqueda para localizar el elemento mximo o mnimo tiene
de los propios elementos, aunque se use para ordenar la cola de prioridad. que examinar todas las posiciones borradas del arreglo, adems de los elementos
Se deja corno ejercicio para el lector el desarrollo de la especificacin de un ADT reales de la cola de prioridad. Si se borraron muchos elementos pero no se ha lle-
para una cola de prioridad. Ahora se revisarn las condiciones de implantacin. vado a efecto una compactacin, la operacin de eliminacin accesar muchos
elementos ms del arreglo que los que existen en la cola de prioridad. Segundo,
Implantacin con arreglo de una cola de prioridad de vez en cuando la insercin requiere accesar todas las posiciones del arreglo
cuando se termina el espacio y comienza la compactacin.
Como ya se vio, se puede implantar. una cola y una pila en un arreglo de tal ma- La operacin de eliminacin coloca una etiqueta de posicin vaca corno en la
nera que cada eliminacin o insercin implique el accesar slo un elemento simple del solucin anterior, pero la insercin se modifica para insertar un nuevo elemento
mismo. Desafortunadamente, esto no es posible en el casode una cola de prioridad. en la primera posicin ''vaca''. La insercin implica entonces el acceso de todos
Supngase que los n elementos de una cola de prioridad pq estn en las posi- los elementos del arreglo hasta alcanzar el primero que fue borrado. Esta dismi-
ciones O a n - l de un arreglo pq.items de tamao maxpq y quepq.rear es igual a la nucin en eficacia. de insercin es la desventaja principal para esta solucin.
primera posicin vaca del arreglo, n. Entonces pqinsert(pq, x) parecera aparente- Cada eliminacin puede compactar el arreglo recorriendo una posicin todos los
mente, ser una operacin bastante directa: elementos a partir del que se elimin y disminuyendo despus en J el valor de
pq.rear. La insercin sigue sin cambiar. En promedio se recorre la mitad de to-
if (pq.rear >= maxpq) dos los elementos de la cola de. prioridad en cada eliminacin, por lo que sta
printf("desborde en la: Cola: de priorida"d") resulta bastante ineficiente. Una posibilidad ligeramente mejor es rec0rrer todos
exit(l); lo.s elementos que preceden a.l eliminado hacia delante o todos los que le suceden
l* fin de 'if *l ; . hacia atrs, dependiendo de qu grupo es menor. Esto requerira guardar los in-
pq.items[pq.rearl X
dicadores ji'ont. y rear y tratar el arreglo como una estructura circular, tal corno
pq.rear++; se hizo con la cola.
En lugar de guardr la cola de prioridad comun arreglo desordenado, hay que
Advirtase que con este rntodo de insercin, los elementos de la cola de prioridad guardarla conio un arreglo circu_lar ordenado de la siguiente manera:
no se guardan de manera ordenada en el arreglo.
La implantacin funciona bien cuando slo hay inserciones. Supngase, sin em-
bargo, que se intenta la operacin pqmindelete(pq) sobre una cola de prioridad aseen, #define MAXPQ
dente. Esto produce dos consecuencias. Primera, todos los elementos del arreglo de struct pqueue {
pq.items[O] apq.itemsfpq.rear-1] deben examinarse para localizar el elemento ms int items[MAXPQJ;
int front, rear;
pequeo. Por lo tanto, una eliminacin requiere accesar todos los elementos de la cola
de prioridad. struct pqueue pq;
Segunda, cmo puede borrarse un elemento que est en el centro del arreglo?
Las eliminaciones en una pila' o cola implican la remocin de un elemento de uno de
los extremos y no requieren bsqueda alguna. La eliminacin en una cola de priori- pq.front es la posicin del elemento menor, pq.rear es mayor en l que la posi-
dad, bajo esta implantacin, requiere de ambas cosas: la bsqueda del elemento que cin del ms grande. La eHrninacin implica nicamente d incremento de
debe eliminarse y la remocin de un elemento es el centro de un arreglo. pq.front (en el caso de una cola ascendente) o la disminucin de pi.rear (en el
caso de una cola descendente). Sin embargo, la insercin requiere la localizacin
Hay varias soluciones a este problema, pero ninguna es enteramente satisfacto- de la posicin exacta del nuevo elemento para luego recorrer los el~mentos que le
ria: suceden o preceden (una vez ms resulta til, la tcnica de corrimiento del grupo
ms pequeo). Este mtodo traslada el trabajo de bsqueda y corrirnierto de la
Se puede colocar un indicador "vaco" en la posicin borrada. Este indicador operacin de eliminacin a la de insercin. Sin embargo, corno el arreglo est
puede ser un valor no vlido como elemento (por ejemplo, -1 en una cola de ordenado, la bsqueda de la posicin del nuevo elemento cuesta, eh promedio,
prioridad de nmeros no negativos), o cada elemento del arreglo puede contener slo la mitad de lo que costara encontrar el elemento mximo o mnimo en un
un campo separado que indique si la posicin est vaca. La insercin procede arreglo desordenado; asimismo, se puede. usar la bsqueda binaria para reducir
como antes, pero cuando pq.rear alcanza el valor de maxpq, los elementos del an ms el costo. Tambin hay otras tcnicas que dejan espacios en el arreglo
arreglo se compactan en el frente del mismo y pq.rear se reinicia como uno ms entre los elementos de la cola de prioridad para permitir inserciones posteriores.
que el nmero de elementos. Esta aproximacin tiene varias desventajas. Prime-

174 Estructuras de datos en C 175


La implantacin en C de pqinsert, pqminde/ete y pqmaxde/ete para la represen- para insertar y eliminar elementos en los extremos izquierdo y derecho de una cola
tacin con arreglo de una cola de prioridad queda como ejercicio para el lector. La doble. Asegrese de que las rutinas funcionan correctamente para la cola doble vaca y
detectan desborde y subdesborde.
bsqueda en arreglos ordenados y desordenados se analiza posteriormente en la sec-
cin 7 .1. En general, el uso de un arreglo no es un mtodo eficaz para implantar una 4.1.10, Defina una cola doble de entrada restringida como una cola doble (vea ejercicio previo)
para la que slo son vlidas las operaciones remvleft, remvright e insrtleft, y una cola
cola de prioridad. En la siguiente seccin y en las secciones 6.3 y 7.3 se examinan im-
doble de salida restringida como una cola doble para la cual slo las operaciones
plantaciones ms eficaces. remvleft, insrtleft e insrtright son vlidas. Muestre cmo se puede usar cada una de ellas
para representar tanto una cola como una pila.
4. 1.11. El estacionamiento Scratchemup tiene un solo carril donde se guardan hasta 10 coches.
EJERCICIOS Los coc~es entran por el extremo sur y salen por el extremo norte. Cuando un cliente
llega por un coche que no est en el extremo norte, se sacan todos los coches que estn
ms al norte de l, se saca ste del estacionainiento y se vuelven a meter, los dems
4.1.1. Escriba la. funcin remvandtest(pq, px, pund) que hace a *pund igual a FALSE, a *px coches en el mismo rden en que estaban. Siempre Que se va un coche, se recorren hacia
igual al iemento eliminado de una cola no vaca *pq y a*pimdigual a TRUE si la cola delante todos los que estaban al sur de ste, de rrianera qlle todos los espacios vacos
est vaca. sfompre estn en la parte sur del estacionamiento.
4.1.2. Qu 'conjunto de condiciones es necesario y suficient para que una secuencia de ope- Escriba un programa que lea un grupo de lneas de entrada. Cada lnea de entrada con-
raciIles inseri y remove sobre una sola cola vaca deje la cola vaca Sin causar subdes- tiene una 'E' para la entrada o una 'S' para la salida y un nmero de placas; Se supone
borde? Qu conjunto de condiciones eS necSario y suficiente para que tal secuencia que los coches llegan y salen en el orden especificado por la entrada. El programa debe-
deje sin cambios una cola no vaca? r imprimir un mensaje cada vez que entra o sale un coche. Cada vez que- entra un
4.1.3.- Cuando un a:rreglo que guarda una cola no se considera circular, el texto sugiere que coche, el mensaje deber especificar si hay o no un lugar para l en el estacionamiento.
cada operadn remove debe recorrer de manera descendente todos los elementos res- Si no lo hay, el coche espera hasta que lo haya o hasta que se lea una lnea de salida para
tantes de la cola. Otra posibilidad es posponer el corrimiento hasta que rear sea igual at' l. Cuando aparece un espacio disponible, se debe imprimir otro mensaje. Cuando un
ltimo ndice del arreglo. Cuando esto ocurre y se intenta efectuar una insercin, toda coche se va, el mensaje debe incluir el nmero de veces que se movi dentro del esta~
la cola se recorre de manera descendente, de modo que el pri_mer elemento de la misma' cionamiento, incluyendo la salida pero no la entrada. Si el carro se retir de la cola de
queda en l.a posicin Odel arreglo. Qu ventajas tiene este mtodo sobre el que recorre espera, este nmero es O.
los elementos cada vez que se realiza una eliminacin? Cules son slls desventajas? 4.1.12. Desarrolle una especificacin de ADT para una cola de prioridad.
Escribir otra vez las rutinas remove, insert y empty usando dicho mtodo. 4.1.13. Implante.una cola de prioridad ascendente y sus operaciones pqinsert, pqmindel(fte y
4.1.4. Demuestre cmo una secuencia de inserciones y eliminaciones puede causar desbrde en empty, usando cada uno de los cuatro mtodos presentados en el texto.
una cola representada mediante un arreglo lineal al intentar insertar un elemento en una 4.1.14. Muestre cmo se clasifica un conjunto de nmeros de entrada por medio de una cola de
cola vaca. prioridad y las operaciones pqinsert, pqmindlete y empty.
4.1.5. Se puede evitar la eliminacin de un elemento de la cola si se agrega un campo qempty a
la representacin de la misma. Muestre cmo puede hacerse lo anterior y escriba otra
vez las rutinas de manipulacin de colas por medio de tal representacin. LISTAS LIGADAS
4.1.6. Cmo puede implantarse unacola de pilas? Una pila de colas? Una cola de colas?
Escribir rutinas para implantar las operaciones apropiadas para cada una de estas Cules son las desventajas de usar memoria secuencial para representar pilas y colas?
estructuras de da.tos. La mayor desventaja, es que una cantidad de memoria fija permanece asignada a la pila
4. l. 7. Muestre cmo implantar una .cola de enteros en Cusando un arrglo queue[IOO], donde o la cola aun cuando stas usen una cantidad menor o incluso ninguna. Adems, no
queue[O] representa el frente de la cola, queue[I] el final y queue[2] hasta queue[99] se puede asignar ms memoria que la que indica esa cantidad fija, con lo que se introdu-
usan para contener los elementos de la cola. Mostrar cmo se inicializa tal arreglo para
ce la posibilidad de desborde. Supngase que un programa usa dos pilas implantadas
representar la cola vaca y escribir las rutinas remove, insert y empty para dicha implan-
en dos arreglos distintos: s l .item, y s2.items. Supngase adems que cada uno de esos
tacin.
arreglos tiene 100 elementos. Entonces, a pesar de que estn disponibles 200 elementos
4~ 1.8. Muestre cmo implantar una cola en C, n la que cada elemento consiste en un nmero 'i1i
ur, 1
para las dos pilas, ninguna de ellas puede contener por separado ms de 100. Aun
variable de enteros.
' in- cuando la primera pila contenga slo 25, la segunda no podr contener ms de 100.
4. l. 9. Una cola doble es un conjunto ordenado de elementos en el cual se pueden realizar
Una solucin a este problema es asignar un arreglo nico items de 200 elemen-
serciones y eliminaciones en cualquiera de los dos extremos. Llame a los extremos de la
cola doble le)~ y right. Cmo puede representarse una cola doble corno un arreglo en tos. La primera pila ocupa de items[O], items[l], ... , items[topl], mientras que la se-
C? Escriba cuatro rutinas en C, gunda pila es colocada en el otro extremo del arreglo, ocupando items[l99],
items[l98], .. , items[top2] en orden descendente. As, cuando una pila no ocupa una
remvleft, remvright, insrtleft, insrtright parte de memoria, la otra puede hacerlo. Desde luego que se necesitan otras rutinas

176 Estructuras de datos en C Colas y listas 177


distintas pop, push y empty para las dos pilas, ya que una crece cuando se incrementa Antes de continuar con el anlisis acerca de las listas ligadas, debe mencionarse
top l, mientras que la otra lo hace cuando disminuye top 2. . . . que stas se presentari de manera primara como una estructura de datos (es decir, un
Desafortunadamente, aunque un esquema como ste permite compartlr un area mtodo de implantacin) en lugar de como un tipo de datos (esto es, como una estruc-
comn, no existe una solucin tan simple para el caso de tres o ms pilas, ni siquiera tura lgica con las operaciones originales definidas en forma precisamente). Por lo
para el de dos colas. En lugar de esto, se tienen que tomar en cuenta los topes Y los tanto, no se presenta aqu una especificacin de ADT para listas ligadas. En la seccin
fondos (o frentes y finales) de todas las estructuras que comp~rten un solo :reglo 9.1, se analizarn las listas como estructuras abstractas y se presentarn algunas ope-
grande. Cada vez que el crecimiento de una estructura trata de violar la memona que raciones originales para ellas.
est usando otra, las estructuras vecinas deben recorrerse dentro del mismo arreglo En esta seccin se presenta el concepto de lista ligada y se muestra la forma de
usarla. En la siguiente seccin se mostrar cmo implantar en C listas ligadas.
para permitir el crecimiento.
En una representacin secuencial, los elementos de la pila o cola estn ordena-
dos de manera implcita por el orden secuencial de memoria. Asi, si q.items[x] re- Insercin y eliminacin de nodos de una lista
presenta un elemento. de una cola, el elemento siguiente ser q.ifems[x + l] (o,
q.items[O], si x es igual a MAXQUEUE -1). Supngase que los elem~ntos de una p1l~ Una lista es una estructura de datos dinmica. El nmero de nodos de una lista
cola se ordenaron de manera explicita, esto es, que cada elemento contenga en s1 puede variar dramticamente cuando se insertan o eliminan elementos. La naturaleza
0
mismo la direccin.del siguiente. Un ordenamiento explicito como se, da origen a dinmica de una lista contrasta con la naturaleza esttica de un arreglo, cuyo tamao
una estructura de datos como la de la figura 4.2.1, que se conoce como lista lineal liga- permanece constante.
da. Cada elemento de la lista se conoce como un nodo y contiene dos campos: uno de Por ejemplo, supngase que se da una lista de enteros, como se ilustra en la figu-
informacin y otro de direccin siguiente. El campo de informacin guarda el elemen- ra 4.2.2a. Se desea agregar el entero 6 al frente de la lista. Es decir, se desea cambiar la
to real de la lista; el de direccin .siguiente contiene la direccin del siguiente nodo de la lista para que quede como la de la figura 4.2.2f. El primer paso es obtener un nodo
lista. Una direccin de este tipo, que se usa para accesar un nodo en particular, seco- donde alojar el entero adicional. Si una lista es una estructura que puede crecer y
noce como apuntador. La lista ligada completa se accesa desde un apuntador externo contraerse,' debe haber algn mecanismo para obtener nodos vacos que se puedan
/ist que apunta al que contiene la direccin del primer nodo de la misma. (Por apunta- agregar a sta. Advirtase que, a diferencia de un arreglo, una lista no tiene un con-
dor "externo'' se entiende que es uno que no est incluido dentro del nodo.. Por el junto suministrado con anterioridad de localidades de memoria en el que se puedan
contrario, su valor puede accesarse de manera directa referenciando una variable). _El guardar elementos.
campo de direcdn siguiente del ltimo nodo de la lista contiene un valor especial. Supngase la existencia de un mecanismo para obtener nodos vacos. La opera-
conocido como null, que no es una direccin vlida. Este apuntador nulo se usa para cin
sealar el final de la lista. . . . .
La lista que no contiene nodos s llama lista vaca o lista nula. El valor del apun- p getnode();
tador externo, /ist, a una lista vaca es el apuntador nulo. Una lista puede inicializarse
como la lista vaca mediante la operacin list = nu/1. obtiene un nodo vaco y pone los contenidos de una variable llamada en la direccin
Ahora se introducirn algunas notaciones que pueden usarse en algoritmos (pero de ese nodo. Entonces el valor de pes un apuntador a ese nodo recin asignado. La
no en programas en C). Si pes un apuntador a un nodo, node(p) se refiere al nodo figura 4.2.2b muestra la lista y el nuevo nodo despus de ejecutar la operacin getno-
apuntado por p; info(p) se refiere a la porcin de informacin del nodo, Y nrx/(p! s': de. Los detalles del funcionamiento de dicha operacin sern explicados en breve.
refiere a la porcin de la direccin siguiente y es, por lo tanto, un apuntador. As1, s1 El paso siguiente es insertar el entero 6 dentro de la porcin info del nuevo nodo
next(p) no es nulo (nu//), info(next(p)) se refiere a la porcin de informacin del nodo asignado. Eso se hace mediante la operacin
que sigue a node(p) en la lista.
info(p) - 6;

in/o sig El resultado de esta operacin se muestra en la figura 4.2.2c.


Despus de asignar el valor correspondiente a la porcin info d'e node(p) es nece-
lnfo slg __.._ L..ln_f,_o_.,_s_/g_.J-I "fe \ :.:, sario hacer lo mismo con la porcin next de dicho nodo. Como node(p) debe insertar-
lista__,_ se en el frente de la lista, el nodo que le sigue ser el primer nodo vigente. Como la
variable list contiene la direccin de este primer nodo, se puede agregar node(p) a la
1 lista por medio de la operacin
nodo nodo nodo nodo

Figura 4.2.1 Una lista lineal ligada. next(p) - list;

Estructuras de datos en C 179


178
info sig info sig que cambia el valor de /is/ al valor de p. La figura 4.2.2e ilustra el resultado de esta
in/o sig
operacin. Advirtase que las figuras 4.2.2e y j son idnticas, excepto en que el valor
lista-! 5
1 ti 3
(a)
ti 8 1 nulo de p no se muestra en la figura 4.2.2f. Esto es porque p se usa como variable auxiliar
durante el proceso de modificacin de la lista, pero su valor es irrelevante para el esta-
do de la misma antes y despus del proceso. Una vez que se ejecutan las operaciones
info sig
anteriores, puede cambiarse el valor de p sin afectar la lista.
p-1 sig info sig info sig
Al unir todos los pasos, se obtiene un algoritmo para agregar el entero 6 al frente
de la lista list:
info

lista__,_ 5
1 :t-1 3
1 +-1 8 nulo 1 p - getnode();
info(p) - 6;
(b) next(p) - list;
in/o sig list = p;

p-1 6

in/o sig In/o sig


Es obvio que el algoritmo puede gneralizarse para que agregue cualquier objeto x al
frente de la lista lis/ si se remplaza la operacin info(p) = 6 por injo(p) = x. Es nece-
in[o sig

lista~ 1 5
+-1 3
ti 8 nulo 1 sario comprobar que el algoritmo trabaja en forma correcta aun cuando la lista est
inicialmente vaca (lis/ = = nu/1).
(e) La figura 4.2.3 .ilustra el proceso de eliminacin del primer nodo de una lista no
in/o sig vaca y el almacenamiento del valor de su campo injo en l variable x. Las configura-

p-1 6
1
11,11: 1s
In/o sig

+-1
info

3
sig

+-1
info

8
sig

nulo
ciones inicial y final se muestran en las figuras 4.2.3a y f, respectivamente. El proceso
. mismo es casi opuesto al proceso en que se agrega un elemento en el frente de una
lista. Para obtener la figura 4.2.3d de la figura 4.2.3a, se ejecutan las siguientes opera-
(d) ciones (cuyas acciones deben ser claras):

infa sig in/o sig in/o sig


(Figura 4.2.3b)
in/o sig p' = list

lis:.=: 1 6
1 ti s t[
(e)
3
ti 8 nulo list =; next(p);
x = inlo(p);
(Figura 4.2.3c\
(Figura 4.2.3d)

En este punto, el algoritmo ya realiz lo que supuestamente deba hacer: elimi-


info _-sig info sig In/o sig nr el primer nodo de la lista lis/ y asignar ax el valor deseado. Sin embargo, el algo-
in/o slg

lista-! 6
ti s
ti
(f)
3
ti 8 nulo ritmo no est completo an. En la figura 4.2.3d, p todava apunta al nodo que antes
era el primero de la lista. Pero este nodo no se usa actualmente, pues ya no es.t en la
lista y su informacin queda almacenada en x. (No se considera que el nodo est en
la lista a pesar del hecho de que next(p) apunte a un nodo, ya que no hay manera de
Figura 4.2.2 Aadiendo un elemento al frente d una lista.
alcanzar node(p) desde el apuntador externo list.)
La variable p se usa como variable auxiliar durante el proceso de eliminacin
del primer nodo. Las configuraciones inicial y final de la lista no hacen referencia a
Esta operacin coloca el valor de /ist (que es la direccin del primer nodo de la lista) en. p. De ah que sea razonable esperar que p ser usado para algn otro propsito, po-
el campo next de node(p). La figura 4.2.2d ilustra el resultado de esta operacin.' codespus de la ejecucin de esta operacin. Pero una vez que se cambia el valor de
En este momento, p apunta a la lista con el elemento adicional incfoido. Sin em; p, no hay manera de accesar el nodo, ya que ni un apuntador externo ni un campo
bargo, como lis! es el apuntador externo a la lista deseada, es necesario modificar next contienen su direccin. En consecuencia, el nodo es intil en este momento y
su valor a la direccin del nuevo primer nodo. Esto puede hacerse ejecutando la ope' ~? puede volverse a usar, aun cuando est ocupando una memoria valiosa.
racin '\ .. Sera deseable cntar con algn mecanismo para dejar disponible node(p) y
~"!.verlo a usar aun cuando se cambie el valor del apuntador p. La operacin que ha-
list p; , :e ce _esto es

Estructuras de datos en q. 181


180
info sig info sig info sig
nodo que se acaba de asignar. No hay garanta de que este nuevo nodo sea el mismo
lista__........ 7
ti 1--+-I 5

(a)
9 nulo que se acaba de liberar.
Otra manera de pensar en ge/nade y freenode es que getnode crea un nuevo
nodo, mientras quejreenode destruye un nodo. Desde este punto de vista, los nodos
no se usan y se vuelven a usar, sino que se crean y se destruyen. Ms adelante se ver
un poco ms acerca de las operaciones getnode y freenode, as como de los concep-
p--
lista-....
7
ti 5

(b)
t-1 9 nulo tos que stas representan, pero antes se har la siguiente observacin interesante.

Implantacin ligada de pilas

La operacin de agregar un elemento al frente de una lista ligada es bastante


P- 7 parecida a la de poner un elemento en una pila. En ambos casos se agrega un nuevo
1 }-
lista__,,,._. , 5
(e)
J-1 9 nulo elemento como el nico inmediatamente accesible de una coleccin. Una pila slo se
puede accesar a travs de su elemento tope, y una lista slo se puede accesar a su pri-
;mer elemento desde el apuntador. De manera similar, la operacin de eliminar el
primer elemento de una lista ligada es anloga a la de sacar un elemento de la pila.
En ambos casos, el nico elemento inmediatamente accesible de una coleccin se eli-
x=7 p- 7
1 }-
lista--11-I
5
(d)
rl 9 nulo
mina de sta, y el siguiente se vuelve inmediatamente accesible.
De este modo se ha descubierto otra manera de implantar una pila. Una pila se
puede representar por medio de una lista lineal ligada. El primer nodo de la lista es el
tope de la 'pila. Si un apuntador externos apunta a una lista ligada de este tipo, la
operacin push(s, x) se puede implantar mediante
r----,----,
x=7 p-- ... : : 1
p = getnode();
' ' ' info(p) x;

+-1
L __ - ~~jS~;~
5 9 nulo next(p) = s;
1 s = p;
(e)

La operacin emply(s) no es ms que la verificacin de que ses igual al apun-


tador nulo (nu/1). La operacin x = pop(s) elimina el primer nodo de una lista no
x=7
h,;ta-+- ~'--5-~-~7 Ll
(f)
_9_.J.._n_u_/o.......J
vaca y seala subdesborde si la lista est vaca:

i f ( empty( s)) {
printf("subdesborde en la pila");
Figura 4.2.3 Eliminacin de un lOdo de! frente de una lista. exit(1);

else
p s,;
freenode(p); (Figure 4.2.3e)
s next(p);
x info(p);
Una vez que se ejecuta esta operacin, es ilegal aludir a node(p), debido, que el no- freenode( p);
do ya no est localizado. Como el valor de pes un apuntador a un nodo que ha sido I* fin de if *!
liberado, tambin es ilegal cualquier referencia a ese valor.
Sin embargo, se podra reubicar el nodo y se podra volver a asignar a p un La figura 4.2.4a ilustra. una pila implantada como una lista ligada, y la 4.2.4b, la
apuntador mediante la operacin p = gelnode(). Advirtase que se dice que el nodo misma pila despus de que se le agreg otro elemento.
"podra ser" reubicado, ya que la operacin getnode regresa un apuntador a algn La ventaja de la implantacin de pilas como listas es que todas las pilas que se
usan en un programa pueden compartir la misma lista disponible. Cuando una pila

182 Estructuras de datos en C Colas y listas 183


Desafortunadamente, vivimos en un mundo real. Las computadoras no tienen
+-1 8 una cantidad infinita de memoria y no tienen la capacidad de fabricar ms para uso
inmediato (por lo menos, no hasta ahora). Por lo tanto, hay un nmero finito de no-
dos disponible y slo se puede usar dicha cantidad en un momento dado. Si se desea
(a)
usar ms nodos que los existentes durante un periodo de tiempo determinado, algu-
nos nodos tendrn que usarse otra vez. La funcin de freenode es hacer que un nodo

+-1 8 -que no se usa en su contexto actual quede disponible para su uso en un contexto dife-
rente.
Puede pensarse que al principio hay un fondo comn y finito de nodos vacos
(b) final ' '
que el programador no puede accesar, excepto a travs de las operaciones getnode y
t f!yenode. getnode elimina un nodo del fondo comn mientras quefreenode lo regre-
>=1 1
4
+-1 l 9
s~'. Comoun nodo sin uso es tan bueno como cualquier otro, da igual qu nodo
r'epera getnode o en qu parte coloca un nodo freenode.
La forma ms natural que puede tomar dicho fondo es la de una lista ligada
(e)
q~e acta como una pila. La lista se liga por medio del campo next en cada nodo. La
operacin getnode elimina' el primer nodo de esta lista y lo deja disponible para su
t-1 9~
us~. La operacin freenode agrega un nodo a,l frente de la lista, dejndolo dispo-
ni,ble para su reubicacin mediante la siguiente aplicacin de getnode. La lista de no-
dos disponibles se denomina lista disponible.
fd)
Qu ocurre cuando la lista disponible est vaca? Esto significa que todos los
Figura 4.2.4 Una pila y una cola como listas ligadas. nodos estn en uso y es imposible asignar uno ms. Si un programa llama a getnode
cuando la lista est vaca, esto significa que la cantidad de memoria asignada a las
estructuras de datos del programa en cuestin es muy pequea. Por lo tanto, ocurre
necesita un nodo, lo puede obtener de dicha lista; cuando ya no lo necesita, lo regre- desborde. Esto es similar a la situacin de una pila implantada en un arreglo, la que
sa a la misma lista disponible. Cada pjla puede crecer y contraerse a cualquier tama- sobrepasa los limites del mismo.
Siempre que las estructuras de datos sean conceptos tericos-abstractos en un
o, siempre y cuando la cantidad total de espacio requerida por todas las pilas, en
mundo de espacio infinito, no hay posibilidad de desborde. Este slo surge cuando
cualquier momento, sea menor que la cantidad de espacio disponible inicialmente
para todas ellas. No se asign con anterioridad espacio para ninguna pilaen particu- se implantan dichas estructuras como objetos reales en un rea finita.
Supngase que un apuntador externo avai/ apunta a una lista de nodos dispo-
lar y ninguna pila usa espacio que no necesite. Adems, otras estructuras de datos,
nibles. Entonces la operacin
como las .colas dobles, pueden compartir tambin el mismo conjunto de nodos.

Las operaciones getnode y freenode


p = getnode();

Ahora se "fetomar el anlisis de las operaciones getnode y freenode. En un se implanta como sigue:
mundo idealizado y abstracto es posible postular un nmero infinito de nodos
nuevos disponibles' para uso de algoritmos abstractos. La operacin getnode i f (avail == null)
printf("desborde");
encuentra dicho nodo y lb deja disponible para el algoritmo. Otra posibilidad es
exit(1);
considerar la operacin getnode como una mquina que fabrica nodos y que nunca
para. Cada vez que se llama a getnode, se presenta ante quien la llama con un nuevo p = avail;
nodo, que es distinto a todos los nodos previamente usados. avail = next(avail); "
En un mundo ideal como se, la operacinfreenode sera innecesaria para ha-
cer que un nodo disponible pueda utilizarse otra vez. Para qu usar un nodo de . Comp la posibilidad de desborde da razn de la operacin getnode, no es nece-
segunda mano cuando una simple llamada a getnode puede producir uno nuevo? H sano mencionar sta en la implantacin de lista de push. Si una pila est a punto de
nico dao que puede provocar un nodo sin uso es reducir el nmero de nodos dis- desbordar la cantidad de nodos disponibles, la instruccin p = getnode() dentro de
ponibles; pero si hay un suplemento infinito de nodos, tal reduccin es insignifican- la operacin push da lugar a un desborde.
te. En consecuencia, no hay razn para volver a usar un nodo.

Estructuras de datos en C y listas 185


184
La implantacin de freenode(p) es directa: ria que el que ocupa un elemento correspondiente en un arreglo, pues en una lista se
necesitan dos campos de informacin para cada nodo (next e info ), mientras que en
next(p) = avail; la implantacin con el arreglo slo se necesita el elemento. Sin embargo, el espacio que
avail = p; para el nodo de una lista no duplica en general el espacio que se usa para un elemen-
to de un arreglo, pues los elementos de tal lista consisten por lo general en estructu-
ras con muchos subcampos. Por ejemplo, si cada elemento de una pila es una estruc-
Implantacin ligada de colas tura que ocupa diez palabras, la adicin de una onceava palabra que contendra un
apuntador incrementara el espacio requerido slo en 10 porciento. Adems, a veces
Ahora se examinar el modo de representar una cola como una lista ligada. es posible comprimir la informacin y el apuntador en una sola palabra, con lo que
Hay que recordar que en una cola los elementos se eliminan del frente y se insertan al se gana espacio.
final. Supngase que el frente de la cola est representado por un apuntador al pri- Otra desventaja es el tiempo adicional gastado en manejar la lista disponible.
mer elemento de una lista y que otro apuntador ligado al ltimo elemento de la lista Cada adicin y eliminacin de un elemento de una pila o cola comprende una elimi-
representa el final de la cola, como se muestra en la figura 4.2Ac. La figura 4.2.4d nacin o una adicin correspondiente a la lista disponible.
ilustra la misma cola despus de insertar un nuevo elemento. La ventaja de usar listas ligadas es que todas las listas y pilas de un programa
Bajo la representacin de lista, una e.ola q consiste en una lista y dos apuntado- tienen acceso a la misma lista libre de nodos. Los nodos que no usa una pila, puede
res q.jront y q.rear. Las operaciones empty(q) y x = remove(q) son completamente usarlos otra, siempre y cuando el nmero total de nodos usado al mismo tiempo no
anlogas a empty(s) y x = pop(s), con el apuntador q.front remplazando a s. Siri sea mayor que el nmero total de nodos disponible.
embargo, se debe poner mucha atencin al eliminar el. ltimo elemento de una cola.
En tal caso, a q.rear tambin se le asigna nu/1 (se le debe asignar el apuntador nlo), La lista ligada como una estructura de datos
ya que en una cola vaca tanto q.rear como q.front deben ser nulos. Por lo tanto, el
algoritmo para x = remove(q) es el siguiente: Las listas ligadas no slo son importantes como un medio para implantar pilas
y colas, sino como estructuras de datos por derecho propio. En una lista ligada, la
i f (empty(q)) { forma de accesar un elemento es recorriendo la lista desde el principio. La implanta-
printf("subdesborde de la cola11 ); in con arreglo permite accesar el n-simo elemento de un grupo por medio de una
exit(1); sola operacin, mientras que en una implantacin con lista se requiere de n opera-
ciones. Es necesario pasar a travs de los primeros n - 1 elementos antes de alcan-
p = q.front; zar el elemento n-simo de una lista, ya que no hay relacin entre la localidad de
x = info(p); memoriaque ocupa un elemento de una lista y su posicin en la misma.
q.front = next(p); Cuando se debe insertar o eliminar un elemenfo en medio de un grupo de otros
i f (q.front == null)
elementos, la lista aventaja al arreglo. Por ejemplo, supngase que se desea insertar
q.rear = null;
un elemento x entre el tercero y cuarto elementos de un arreglo de tamao 10 que en
freenode(p);
return ( x); ese momento contiene siete elementos (x[O] hasta x[6]). Primero hay que mover una
casilla los elementos 6 a 3, e insertar el nuevo elemento en la posicin 3, recientemen-
La operacin insert(q, x) se implanta mediante te desocupada. En la figura 4.2.Sa se ilustra este proceso. En este caso, la insercin
de un elemento propicia el movimiento de cuatro elementos, adems de la insercin
misma. Si el arreglo tuviera 500 o 1000 elementos, tambin tendra que moverse el
p = getnode(); enorme nmero de elementos correspondiente. De manera anloga, para eliminar
info(p) = x; un elemento de un arreglo sin dejar espacio, se deben recorrer una posicin todos los
next(p) = null; elementos que estn ms all de ste.
i f (q.rear == null)
Por otra parte, supngase que los elementos estn almacenados como una lis-
q.front = p;
ta. Si p apunta a un elemento de la lista, la insercin de un elemento nuevo despus
else
next(q.rear) p; de node(p) implica la ubicacin de un nodo, la insercin de la informacin y el ajus-
q.rear = p; te de dos apuntadores. La cantidad de trabajo iequerida es independiente al tamao
de la lista. En la figura 4.2.Sb se ilustra esto.
Cules son las desventajas de representar una pila o cola mediante una lista li- Supngase que insfter(p, x) denote la operacin que inserta un elemento x
gada? Resulta evidente que en una lista ligada un nodo ocupa ms espacio de memo' dentro de una lista despus de un nodo apuntado por p. Esta operacin se implanta
de la siguiente manera:

186 Estructuras de datos en C Colas y listas 187


xo xo xo
XI XI XI

X2 X2 X2

X3
~

X4 X3 X3

X5 X4 X4
.

X6 xs X5

X6 X6

(a) Figura 4.2.5

q - getnode();
info( q) x;
next(g) = next(p);
next(p) = g;

Un elemento puede insertarse slo despns de un nodo determinado, no antes.


Esto es porque no hay manera de proceder d.e un nodo determinado a su antecesor
en una li~ta lineal sin recorrer la lista desde el principio. Para insertar un elemento
antes que node(p) es necesario cambiar primero el campo next de su antecesor para
apuntar al nodo recin ubicado .. Pero, al determinar p, no hay manera de encontrar
ese antecesor. (Sin embargo, se puede producir el efecto de insercin de un elemento
antes de node(p) insertando el elemento inmediatamente despus de node(p) e inter-
cambiando despus la informacin info(p) cdn el campo info del sucesor recin
creado. Al lector se le dejan los detalles.)
Anlogamente, para eliminar un ndo de una lista lineal no basta con dar un
apuntador a. ese nodo. Esto es porque el campo next del antecesor del nodo debe
cambiarse para que apunte al sucesor del nodo, y no hay manera directa de alcanzar
el antecesor de un nodo dete.rminado. Lo mejor que puede h.acerse es eliminar un no-
do que sigue a otro determinado. (Sin embargo, es posible salvar los cante.nidos del
nodo siguiente, eliminar ste y luego remplazar los contenidos del nodo determinado
con la informacin salvada. Esto produce el efecto de eliminar un no.do determinado
a menos que ste sea el ltimo de la lista.)
Supngase que de/after(p, x) denota la operacin de eliminar el nodo que sigue
a node(p) y asignar su contenido a la variable x. Esta operacin puede implantarse
como sigue:

188 Estructuras de datos en C Colas y listas 189


q = null;
far (p = list; p != null && x > info(p); p = next(p))
next(p) = next(q);
q = p;
I* En este momento, un nodo que contenga x deber insertarse *
freenode( q);
if ( q = = null) /* insertar x a la cabeza de la lista *
push(lst, x);
El nodo liberado se coloca dentro de la lista disponible de manera que pueda else
usarse otra vez en el futuro: insater(q, x);

Ejemplos de operaciones con listas Esta operacin, que es muy comn, ser denotada por place(list, x).
Examnese la eficacia de la operacin place. Cuntos nodos se accesan en pro-
Enseguida se ilustran estas dos operaciones, as como las operaciones push y medio para insertar un elemento nuevo en una lista ordenada? Asmase que la lista
pop para listas, con algunos ejemplos simples. El primer ejemplo consiste en borrar c.ontiene n nodos. Entonces x puede insertarse en una de las n + 1 posiciones; esto
todas los incidentes del nmero 4 en una lista !ist. Se recorre la lista para buscar los es, se puede encontrar que x es menor que el primer elemento de la lista, que est
nodos que contengan el nmero 4 en sus campos info, y se debe borrar cada uno de entre el primero y el segundo, ....... entre el (n - 1)-simo y el n'simo y que es ma-
stos de la lista. Pero para eliminar un nodo de la lista, hay que conocer a su prede- yor que este ltimo. Si x es menor que el primero, place accesa slo el primer nodo
cesor. Por tal razn se usan dos apuntadores, p y q. p se usa para recorrer la lista y de la lista (aparte del nodd nuevo que contiene ax); esto es, determina de inmediato
q apunta siempre al predecesor de p. El algoritmo se vale de la operacin pop para que x < info(lisl) e inserta un nodo que contiene x que se vale de push. Si x est entre
eliminar nodos del principio de la lista y de delafter para eliminar nodos de la parte el k-simo y el (k + 1)-simo elemento, place accesa los primeros k nodos; slo des-
media. pus de verificar que x es menor que el contenido del (k + l)-simo nodo se inserta x
por medio de insafter. Si x es mayor que el n-simo elemento, se accesan todos los
q = null; nodos n.
p = list Supngase ahora que tambin hay probabilidades de que x se inserte en cual-
~hile (p != null) { quiera de las n + l posiciones posibles. (Sr esto es cierto, puede decirse que la inser-
i f (info(p) == ,) cin es aleatoria.) Entonces la probabilidad de hacer la insercin en una posicin
i f (q == null) particular es 1/(n + l). Si el elemento se inserta entre la posicin k-sima y la (k +
., .
I* eliminar el primer nodo de la lista */ 1)-sima, el nmero de accesos es k + I. Si se inserta despus del n-simo elemento,
x = pop(lst);
el nmero de accesos es n. El nmero promedio de nodos accesados, A, es la suma,
p = list;
)
en todas las posibles posiciones de insercin, de los productos de la probabilidad de
else { insertar en una posicin particular y del nmero de accesos requeridos para insertar
I * eliminar el nodo despus de q y mover p *I un elemento en dicha posicin. As
p = nexl(p);
delafter(q, x);

else
I* fin deif *I
A =(n : ) * 1+ ( ) * 2 + + ( 1- ) * (n -
n :
n+I
l)
n+l
1
+ (-) *n
I* continuar el recorrido de la,, lista *f
l '
p
q = Pi
= next(p); +( ~)*n
I* fin deif *I
I* fin de while */,
o
.,"'
La prctica de usar dos apuntadores, uno que sigue al otro, es muy comn al A = (.-1- )"(l + 2 + + n) + -n-
trabajar con listas. Esta tcnica se usa tambin en el siguiente ejemplo. Supngase
n + I n+I
que una lista lis/ est ordenada de menor a mayor. A una lista de este tipo se la llama Ahora 1 + 2 + ... + n = n * ni 1
(Esto puede probarse fcilmente por me-
lista ordenada. Se desea insertar un elemento x en esta lista, en el lugar correspon- dio de induccin matemtica). Por lo tanto
diente. Para hacer esto, el algoritmo se vale de la operacin push para agregar un
nodo al frente de la lista y de la operacin insafter para agregar uno en medio de la
lista:
A=(n:1) *(n *n-+2- )1 + - n
- = n + -n -
n + 1 2 n + 1

Estructuras de datos en C Colas y. listas 191


190
Cuando n es grande, nl(n + 1) se acerca a 1, de manera que A es en forma aproxi-
mada n/2 + 1 o (n + 2)/2. Paran grande, A est muy cercana a n/2, por lo que a
menudo se dice que la operacin de insercin aleatoria de un elemento en una lista
ordenada requiere de casi n/2 accesos de nodo en promedio.

Implantacin con lista de colas de prioridad

Una lista ordenada puede usarse para representar una cola de prioridad. Para
la cola de prioridad ascendente se implanta la insercin (pqnsert) mediante la opera-
cin place, la que mantiene en orden la lista, y la eliminacin del elemento mnimo
(pqmindelete) mediante la operacin pop, la que elimina el primer elemento de la lis-
ta. Una cola de prioridad descendente puede implantarse guardando la lista en orden
rn
descendente, en lugar de ascendente, o usando remove para implantar pqmaxdelete.
Una cola de prioridad implantada corno lista ordenada ligada necesita examinar un
nmero promedio de casi n/2 nodos para la insercin, pero un solo nodo para la eli-
minacin.
Una lista no ordenada puede usarse tambin corno cola de prioridad. Una lista
rn '
N
"'
:!;

li
de este tipo slo necesita examinar un nodo para la insercin (implantando pqnsert.
"o
por medio de push o nsert), pero para la eliminacin debe examinar los n elementos
(recorrer la lista entera para encontrar el mnimo o mximo y luego eliminar ese no-
do). As, una lista ordenada es un tanto ms eficaz que una no ordenada en la
implantacin de una cola de prioridad.
La ventaja de una lista sobre un arreglo en la implantacin de una cola de
rn ~

"' "
00
o
"' rn """'o
"'o
"'oe
e
o
o
prioridad es que en la primera no se necesita el recorrirniento de elementos. Se puede i{l
insertar un elemento en una lista sin mover otro, mientras que en un arreglo es impo- ~
sible hacerlo a menos que se deje un espacio vaco. En las secciones 6.3 y 7 .3 se exa- .,
r, ;::;
"'<si

~~t
"' ""
minan otras implantaciones de la cola de prioridad ms eficaces.
3 e "' - .=
~
~

Nodos cabecera '" :ce ~


r
A veces es deseable guardar un nodo extra en el frente de una lista. A. tal nodo,
que no representa un elemento de la lista, se le llama nodo cabecera o cabecera de la ,- :: r,
00 00
~
lista. Puede que la porcin info del nodo cabecera no se use, corno se observa en la "' 1

figura 4.2.6a. Ms a menudo, la porcin info de dicho nodo se usa para guardar in- 1
formacin global acerca de toda la lista. Por ejemplo, la figura 4.2.6b ilustra una lis- '
ta en la que la porcin nfo del nodo cabecera contiene el nmero de nodos (sin
incluir la cabecera) de la lista. En una estructura de datos corno sta se necesita
trabajar ms para agregar o eliminar un elemento de la lista, debido a que hay que ., .,"'
,-
ajustar el conteo en la cabecera de la lista. Sin embargo, el nmero de elementos de "'
la lista puede obtenerse de modo directo del nodo cabecera sin atravesar toda la lista.
Otro ejemplo del uso de nodos cabecera es el siguiente. Supngase que en
una fbrica se ensambla maquinaria en pequefias unidades. Una mquina particular
~t
~ Jt Jt Jt "'

(nmero de inventario A746) podra estar compuesta de un nmero de partes dife-


rentes (nmeros B841, K321, A087, 1492, 0593). Este montaje estara representado
por una lista corno la de la figura 4.2.6c, donde cada artculo de la lista representa
un componente y el nodo cabecera todo el montaje.

192 Estructuras de datos en C Colas y listas 193


1
'
La lista vaca ya no estara representada por el apuntador nulo, sino por una
lista con un nodo cabecera simple, como en la figura 4.2.6d. 4.2.4, Escriba algoritmos para ejecutar cada una de las operaciones anteriores sobre un grupo
Por supuesto que los algoritmos para operaciones como empty, push, pop, in- de elementos en posiciones contiguas de un arreglo.
sert y remove se tienen que escribir de nuevo de manera que expliquen la presencia 4,2.5. Cul es el nmero promedio de nodos accesados cuando se busca un elemento parti-
del nodo cabecera. Casi todas las rutinas se vuelven un poco ms complejas, pero al- cular en una lista desordenada? En una ordenada? En un arreglo desordenado? En
uno ordenado?
gunas, como insert, se vuelven ms simples, ya que un apuntador externo a la lista
nunca es nulo. El lector se queda con la reescritura de las rutinas como ejercicio. Las 4.2-.6. Escriba algoritmos para pqinsert y pqmindelete para una cola de prioridad ascendente
implantada como una lista desordenada y como una ordenada.
rutinas insafter y dela/ter no necesitan ningn cambio. En realidad, cuando se usa
un nodo cabecera, pueden usarse insafter y dela/ter en lugar de push y pop, debido a 4,2. 7. Escriba algoritmos para ejecutar cada una de las operaciones del ejetcicio 4.2.3, supo-
niendo que cada lista contiene un nodo cabecera con el nmero de elementos de la lista.-
que el primer elemento de una lista como sta aparece en el nodo que sigue al nodo
cabecera, en lugar de aparecer en el primer nodo de la lista. 4:2.s. Escriba un algoritmo que d como resultado un apuntador a-un nodoque contiene un
Cuando la porcin info de un nodo puede contener un apuntador, se presentan elemento x en una lista: con un nodo cabecera. El campo:in}O de la cabecera deber
contener el apun1ador que .recorre la lista.
posibilidades adicionales para el uso de un nodo cabecera. Por ejemplo, la porcin
info de la cabecera de una lista podra contener un apuntador al ltimo nodo de la
lista, como se muestra en la figura 4.2.6e. Una implantacin de este tipo simplifica
LISTAS EN C
la representacin de una cola. Hasta el momento fueron necesarios dos apuntadores
externos, front y rear, para que una lista representara una cola. Sin embargo, ahora
Implantacin con arreglo de listas
slo es necesario un apuntador externo, q, al nodo cabecera de la lista. next(q)
apunta al frente de la cola e info(q) al final.
Cmo pueden representarse listas lineales en C? Como una lista no es ms
Otra posibilidad para el uso de la porcin info de la cabecera de una lista es co-
que una coleccin de nodos, un arreglo de nodos se sugiere a s mismo de inmediato.
mo apuntador a un nodo "vigente" de la lista durante el proceso de recorrido. Esto
Sin embargo, es posible que los nodos no estn ordenados d.e acuerdo con el orden
eliminara la necesidad de un apuntador externo durante el recorrido.
del arreglo; cada uno de ellos debe contener en s mismo un'apuntador a su sucesor.
As, un grupo de 500 nodos podra declararse como un arreglo node del modo
EJERCICIOS siguiente:

4.2.1. Escriba un conjunto de rutinas para implantar varias colas y pilas dentro de un solo #define NUMNODES 500
arreglo. struct nodetype {
4.2.2. Cules son las ventajas y cules las desventajas de representar un grupo de elementos int info, next;
como un arreglo en oposicin a una lista lineal ligada? J;
4.2.3. Escriba .un algoritmo para ejecutar cada una de las siguientes operaciones: struct nodetype node[NUMNODESJ;
a. Agregar un elemento al final de una lista.
h. Concatenar dos listas.
c. Liberar todos !os nodos de una lista. En este esquema, un apuntador a un nodo se representa mediante un ndice del
d. Invertir una lista de manera que el ltimo elemento se convierta en el primero, y as arreglo. Un apuntador es un entro entre O y NUMNODES ~1 que alude a un ele-
sucesivamente. mento particular del arreglo node. El apuntador nulo se representa por el entero
e. Elimine el ltimo elemento de una lista. -1. En esta implantacin, la expresin node[p) en C, se usa para eludir node(p); in-
f. Elimine el n-simo elemento de una lista. Jo(p) se alude mediante node[p J.info, y next(p) son referenciadas por node[p J.next.
g. Combine dos listas ordenadas en una sola que tambin est ordenada. 1111/I est representado por -1.
h. Forme una lista que contenga la unin de los elementos de dos listas. Por ejemplo, supngase que la varia.ble list representa un apuntador a una lis-
i. Forme una lista que contenga la interseccin de los elementos de dos listas. ta. Si list tiene el valor 7, node[7] es el primer nodo de la lista, y node[7].info es el
j. Insertar un elemento despus del n-simo de una lista. primer elemento de datos. El segundo nodo de 1a list est dado por node[7].next.
k. Elimine todos los segundos elementos de una lista.
Supngase que node[7].next es igual a 385. Entonces node[385].info es el segundo
l. Ponga los elementos de una lista en orden ascendente.
elemento de datos en la lista y node[385].next apunta al tercer nodo.
m. D como resultado la suma de los enteros de una lista.
n. D como resultado el nmero de elementos de una lista.
Los nodos de una lista pueden estar dispersos en todo el arreglo nade en cual-
o. Mueva node(p) n posiciones hacia adelante de una lista. quier orden. Cada nodo lleva dentro de s la ubicacin de su sucesor hasta el ltimo
p. Haga una segunda copia de una lista. nodo de la lista, cuyo campo next contiene -1, que es el apuntador nulo. No hay re-
lacin entre los contenidos de un nodo y el apuntador a ste. El apuntador, p, a un

194 Estructuras de datos en C


Colas y listas 195
nodo slo especifica qu elemento del arreglo node se referencia; node[p].info es global avail se us~ p~ra apuntar a la lista disponible, esta lista se puede organizar ini-
quien representa la informacin contenida dentro de ese nodo. cialmente de la s1gmente manera: .
La figura 4.3.l ilustra una porcin de un arreglo node que contiene cuatro lis-
tas ligadas. La lista lisll comienza en node[16] y contiene los enteros 3, 7, 14, 6, 5, avail = O;
37, 12. Los nodos que contienen estos enteros en su campo info estn dispersos a lo for (i - O; i < NUMNODES-1; i++)
largo del arreglo. El campo next de cada nodo contiene el ndice dentro del arreglo node[iJ.next = i + 1;
del nodo que co:1tiene el siguiente elemento de la lista. El ltimo nodo ,de la lista es ,node[NUMNODES-1].next - -1;
node[23], el que contiene el entero 12 en su campo info y el apuntador nulo (-1) en
su campo next, para indicar que es el ltimo de la lista. Los 500 nodos estn ligados al principio en su orden natural, de manera que
De manera anloga, /is/2 comienza en el node[4). y ,contiene los enteros 17 y 26; node[i] apunta a node[i + !]. node[O] es el primer nodo de la lista disponible;
/is/3 comienza en node[ll] y contiene a 31, 19 y 32, y /is/4 comienza en node[3] y node[I] el segundo, y as sucesivamente. node[499] es el ltimo de la lista, ya que no-
contiene los enteros l, 18, 13, 11, 4 y 15. Las variables/istl, lis/2, list3 Y lis/4 son de[499].next es igual a -1. No hay otra razn para este orden inicial que la conve-
enteros que representan apuntadores externos a las cuatro listas. As, el hecho de niencia. Tambin se pudo haber puesto node[O].next como 499; node[499].next
que la variable /is/2 tenga el valor 4 representa el hecho de que la lista a la cual apun- como l, node[l].next como 498, y as sucesivamente, hasta asignar node[249].next a
ta sta comience en node[4]. 250 y node[250] .next a -' l. El aspecto importante es que el ordenamiento resulta
Al principio no se usa ningn nodo, ya que no se han formado an las listas. explcito dentro de los propios nodos y no como consecuencia de otra estructura
En consecuencia, todos tienen que ser colocados en la lista disponible. Si la variable subyacente.
Para el resto de las funciones de esta seccin, puede suponerse que las variables
nade y avail son globales y que cualquier rutina puede, por lo tanto, usarlas.
Cuando se necesita un nodo para aplicarlo en una lista particular, ste se ob-
o 26 1 tiene de la lista disponible. De manera anloga, cuando un nodo ya no se necesita, se
l 11 9 devuelve a la lista disponible. Estas dos operaciones estn irnplantadas por medio de
2 5 15 las rudnas en C getnode y jreenode, getnode es una funcin que elimina un nodo de
lista4 = 3 1 24 la lista disponible y regresa un apuntador hacia l:
lista2=4 17 o
5 13 l
6 'getnode ()
7 19 18 {
8 14 12 int p;
9 4 21 if (avail -- -1)
10 printf("desborde \ n" ) ;
lista3 = 11 31 7 exit(1);
12 6 2
13 ' p = avail;
14 avail = node[availJ.next;
15 37 23 return(p);
lisia f' = 16 3 20 I * fin de getnode *I
17
18 32 -1
19 Si avail es igual a -1 cuando se llama a esta funcin, no hay nodos disponibles. Esto
20 7 8 significa que las estructuras de la lista de un programa particular rebasaron el espa- ~,
21 15 -1 cio disponible. '

22 La funcin freenode acepta un apuntador a un nodo y devuelve ese nodo a la


23 12 -1 lista disponible:
24 18 .5
25 "'-
freenode(p)
26 ' Figura 4.3.1 Un arreglo de nodos
int p;
" que contiene catro tist~s lig.das.
{

196 Estructuras de datos en.e Cofs y listas 197


Limitaciones de la implantacin con arreglo
node[pJ.next avail;
avail = p; Como se vio en la seccin 4.2, la nocin de un apuntador permite construir y
return; manipular listas ligadas de varios tipos. El concepto de apuntador introduce la posi-
/* fn de freenode */ bilidad de agrupar una coleccin de bloques construidos, llamados nodos, dentro de
estructuras flexibles. Alterando los valores de los. apuntadores, los nodos pueden
Las operaciones originales para listas son versiones directas en C de los algoritmos conectarse, desconectarse y agruparse otra vez en configuraciones que crecen y se
correspondientes. La rutina insafter acepta un apuntador p a un nodo Yun elemento contraen mientras progresa la ejecucin de un programa.
x como parmetros. Primero se asegura que p no sea nulo y luego inserta x dentro En la implantacin con arreglo, se establece un conjunto fijo de nodos median-
del nodo que sigue al apuntado por p: te un arreglo al principio de la ejecucin. Un apuntador a un nodo se representa por
medio de la posicin relativa del nodo dentro del arreglo. La desventaja de este enfo-
que es doble. Primero, con frecuencia no ruede calcularse el nmero de nodos que
insafter(p, x) se necesita cuando se escribe el programa.. Los datos con los que se ejecuta el progra-
int p, x; . ma), menudo determinan el nmero de nodos necesario. As, no importa cuntos
{ elementos contiene el arreglo de nodos, siempre es posible que el programa se ejecu-
int q; te con datos que requieran una mayor cantidad.
if ( p == -1 j { . La segunda. desventaja del enfoque con arreglo es que cualquier nmero de
PriDJf ("insercin no efectuada/n:"); nodos que se declare debe permanecer asignado al programa a lo largo de la ejecu-
return;
cin. Por ejemplo, cuando sedeclaran500 nodos de determinado tipo, se reserva la
q i= getnode(); cantidad de memoria que stos requieren para este propsito. Si el programa slo
node[tj].info .x; usa 100, o Incluso 10, en su ejecucin, los nodos adicionales permanecen en reserva y
node[q].~ext node[p].next; la, memoria que les corresponde no puede usarse para otro propsito.
node[p].next q; La solucin a este problema es permitir que los nodos sean dinmicos en lugar
return; de estticos. Es decir, siempre que se necesita un nodo, se reserva memoria para l, y
/* fin de insafter *I cuando se deja de ocpar, se libera la memoria. As, la memoria para los nodos que
ya no estn en uso, puede usarse en algn otro .propsito. Tampoco se establece un
lmite predefinido sobre el nmero de nodos. Siempre que est disponible la memos
La rutina de!aj/er(p, px), llamada mediante la instruccin de!after(p, &p), eli-
ria suficiente para la tarea como un todo, se puede reservar parte de dicha memoria
mina el nodo que sigue a node(p) y almacena su contenido en x: para su uso como nodo.

delafter(p, px) Asignacin y liberacin de. variables dinmicas


int p, *px;
{ .. iir{ las secciones l. l, l.2 y 1.3 se examinaron los apuntadores en el lenguaje C
int q; Si x es un objeto, &x es un apuntador ax. Si pes un apuntador en C, pes el objeto
if ((p == -1) 11 (node[pl.next -1)) al que apunta p. Se pueden usar apuntadores en C para ayudar a implantar listas li-
prinU ("eliminacin no efectuada/n");
gadas dinmicas. Sin embargo, primero se analizar cmo se puede asignar y liberar
return;
memoria de manera dinmica y cmo se accesa en C la memoria dinmica.
q node[p].next; En C puede crearse una variable apuntador a un entero mediante la declara-
*px = node[qJ.info; cin
node[pJ.next = node[qJ.next;
freenode ( q) ; int *p;
return;
I * fin de delafter *I Una vez que se dec\ara la variable p como apuntador a un tipo de objeto especfico,
se tiene la capacidad de crear un objeto de ese tipo especfic0 de manera dinmica y
Antes de llamar a insafter hay que asegurarse de que p no es nulo. Antes della- asignar su direccin a p.
mar a de!aj/er hay que asegurarse de que ni p ni nodefp] .next lo son.

Colas y listas 199


198 Estructuras de datos en C
En C, esto puede hacerse llamando a lti funcin malloc(size) de la biblioteca En la lnea 3 se crea una variable entera y su direccin se pone en p. La lnea 4
estndar. mal/oc asigna una porcin de memoria de manera dinmica de tamao asigna a 3 al valor de esa variable. La 5 asigna a q la direccin d la misma. La ins'
size y da como resultado un apntador a un elemento de tipo char. Considrese las trccin de asignacin en la lnea 5 es perfectamente vlida, ya que a una variable
declaraciones apu.nt~dor (q) se le est asignando el valor de otra (p)c La figura 4.3.2a ilustra : si-
tuacin despus de la lnea 5. Obsrvese que, a partir de ese momento, *p y *q se
extern char *malloc(); refieren a la misma variable.'La lnea 6, pr htanto, imprime el contenido de dicha
int *pi; variable (que es 3) dos veces.
float *pr; La lnea 7 asigna valor 7 a una variable de tipo entera, x. La 8 .cambia el valor
de *q ax. Sin embargo, como p y q son apuntadores a la misma variable, tanto *p
Las instrucciones como *q tienen el valor 7. En la figura 4.3.2b se ilustra esto. Por lo tanto, la lnea 9
ffiprime dos veces el nmero 7.
pi (int *) malloc(sizec:,f (int)); La lnea I Ocrea una nueva variable entera y pone su direccin en p. En la figu-
pr (float *) mallc:,c(sizof (float)); ra 4.3.2c se ilustran los resultados. *p se refiere ahora a la variable entera recin
creada, a la que an no se le asigna ningn valor. q.,no ha sido cambiada; por lo tan-
crean.de manera dinm.icaJa vadable entera *piy la de punto flotante *pr. Estas va- to, el valor de *q sigu siendo 7. Aavirtase que *p no se refiere a una variable
riables se llama variables dinmicas. En la ejecucin de estas instrucciones, el opera- simple especfica; su valorcambia como el de p. La lnea 11 asigna el valor 5.a esa
dor sizeofdacomo resultado el/amao, en bytes,.desubperando.cEstese usa para variable recin creada, como se ilustra en la figura 4.3.2d, y la 12 imprime los valo-
mantener la independencia de la mquina. mal/oc puede entonces crear un objeto de res 5 y 7.
ese tamao. A,s, malloc(sizeof(int)) asigna memoria para. un entero, mientras que La funcin free se usa en C para liberar memoria de una variable asignada de
malloc(sizeof(float)) lo hace 'para un nmero de punto flotante .. mal/oc tambin manera dinmica. La instruccin
da como resultado un apuntador a la memoria que asigna. Este apunta al. primer
free ( p) ;
byte (por ejemplo, carcter) de esa memoriay es de tipochar*. Para obligar a que este
apuntador apunte a un entero o real, se usa el operador de cambio de tipo (int*) o
(float*).
(El operador sizeofda como. resultado un'valor de tipo (int), mientras cue la p
funcin mal/oc espera un parmetro de tipo sin signo. Para que el programa est
"limpio", debe escribirse: q ---
(a)

p---
X
pi= (int *) malloc ((unsigned)(sizeof (in't)));

Sin embargo, con frecuencia se omite el cambio de tipo sobre el operador sizeof.)
Para ejemplificar el uso de la funcin mal/oc y de los apuntadores, considrese
q--- D (h) '
D
las siguientes instrucciones:
q
1 int *P, *q;
2 int X

,4
3 p = (int *) malloc(sizeof (int));
*P --= 3;
p---
5 q = p;
(e)
6 printf ( 11 %d %d\n 11 , *p, *q);
7 X= 7;
*q = X; q
B
9
10
11
12
p =
*P := 5;
printf(%d %d\n 11
*P, *q);
printf( 11 %d %_d\n 11

,
,

(int *) malloc (sizeof (int));

*p, *q);
P--- otJEJ (d) Figura 4.3.2

~ Estructuras de datos en C 201


1
-D q-D
hace que cualquier referencia futura a.la vari.able *p sea ilegal (a menos que, por su-
puesto, se asigne un nuevo valor a p mediante una instruccin de asignacin o una p
llamada a mal/oc). Llamar afree(p) hace que la memoria ocupada por *p quede dis-
ponible para que se vuelva a usar si es necesario. (a)
(Nota: La funcin free espera por omisin, un parmetro apuntador de tipo
char*. Para que la instruccin sea "limpia" debe escribirse.

free( (char *) p);


P, ---+-1
1
,-----1
1

L - - ___
1
1
1
_J
q-D
Sin embargo, en la prctica se omite con frecuencia el cambio de tipo del parmetro.) lb)
Para ilustrar el uso de la funcin free, considrese las siguientes instrucciones:

1
2
3
4
p = (int *) m~lloc ( s izeof (int));
*P = s;
q i= ( int *) ma11oc (sizeof (int));
*q = 8;
r-o
q-

5 free(p); <r)
6 p = q
7
B
g
q = (int *) malloc (sizeof (int));
*q = 6.
printf(' "%'d %d\n 11 , *p, *q); q-D "__..O
l<l) Figura, 4.3.3
Se imprimen los valores 8 y 6. La figura 4.3.3a ilustra la situacin despus de la
lnea 4, donde se ha ubicado a *p y *q y se les ha asignado valor. La figura 4.3.3b
muestra el efecto de la lnea 5, en la que se ha liberado la variable a la que apunta p.
La figura 4.3.3c ilustra la lnea 6, en la que se cambia el valor de p para apuntar a la para permitir que el valor cero de un apuntador se escriba como NULL. Este valor
variable *q. En las lneas 7 y 8 se cambia el valor de q para apuntar a la variable de apuntador NULL no hace referencia a una localidad de memoria, sino que deno-
recin creada a la que se da valor 6 en la lnea 8 (figura4.3.3d). ta el apuntador que no apunta a nada. El valor NULL (cero) se puede asignar a cual-
Advirtase que si se llama a mal/oc dos veces seguidas y se asigna su valor a la quier variable apuntador p, luego de lo cual es ilegal una referencia a *p.
misma variable, como en: Obsrvese que una llamada afree(p) hace ilegal una referencia posterior a.*p.
Sin embargo, los efectos reales de una llamada a free no estn definidos por el len-
p =. ( in t *) malloc (sizeof (int)); guaje C; cada implantacin de C tiene la libertad de desarrollar su propia versin de
*P = 3; esta funcin. En la mayora de stas, se libera la memoria para *p, pero el valor dep
p = (int *) malloc (sizeof (int) ); se deja intacto. Esto significa que aunque una referencia a *p se vuelve ilegal, quiz
*P = 7; no haya manera de detectar la ilegalidad. El valor de p es una direccin vlida y el
objeto del tipo adecuado en esa direccin puede usarse como el valor de *p. p recibe
se pierde la primera copia de *p, debido a que su direccin no se salv. El espacio el nombre de apuntador incone.i;o. Al programador le corresponde no usar nunca tal
asignado para variables dinmicas slo puede accesarse a travs de un apuntador, A apuntador en un programa. Una buena costumbre es hacer p igual a NULL despus
menos que el apuntador a la primera variable se salve en otro apuntador, dicha va- de ejecutar free(p) ..
riable se perder. De hecho, ni siquiera puede liberarse su memoria, porque no hay Debe mencionarse otracaracterstca peligrosa a'sociada a los apuntadores. Si
manera de hacer referencia a ella en una llamada afree. Este es un ejemplo en el que p y q son dos apuntadores con el mismo valor, las variables *p y *q son idnticas.
se asigna memoria que no se puede referenciar. Ambas, *p y *q, se refieren al mismo objeto. As, una asignacin a *p cambia el va-
En un programa C, el valor O (cero) puede usarse como el apuntador nulo. lor de *q, a pesar de que ni q ni *q estn mencionadas explcitamente en la instruc-
Cualquier variable apuntador puede tomar este valor. Por lo general, el encabeza- cin de asignacin a *p. Al programador le corresponde tomar en. cuenta "qu
miento estndar de un programa en C incluye la definicin apuntadores apuntan a qu" y reconocer la ocurr.encia. de resultados implcitos
como los mencionados.
#define NULL o

202 Estructuras de datos en C Colas y listas 203


Listas ligadas por medio de variables dinmicas deber regresar el nodo cuya direccin est en p a la memoria disponible. Ahora se
presenta la rutina freenode:
Ahora que ya se cuenta con la capacidad de asignar y liberar de manera din-
mica una variable, a continuacin se ver cmo pueden usarse las variables dinmi- freenode(p)
cas para implantar listas ligadas. Hay que recordar que una lista ligada consiste en NODEPTR p;
un conjunto de nodos, cada uno de los cuales tiene dos campos: uno de informacin {
y un apuntador al siguiente nodo de la lista. Adems, un apuntador externo apunta free ( p) ;
al primer nodo de la lista. Para implantar apuntadores a una lista, se usan variables
apuntadores. Asi, el tipo de un apuntador y de un nodo se define por
El programador no debe ocuparse del manejo de la memoria disponible. Ya no
struct node se. nf~esita el apuntador a~aif (que apunta al primer nodo. disponible), pues el siste-
int info; niafgobierna Ia asignacin y l~ liberacin ,<le.r10dos y tof[\~ en cuenta el primer nodo
struct node *next; disponible. A.dvirtase tambin. (JU<! J.O se. Vf;I"ifica .en g,tnode Si. OCUrri desborde.
}; E;to esporque sernejnte condici11 ~era detectada d.urante la ejecudn de la fun-
typedef struct node NODEPTR; cin inallo: y es porque depende del sistern. . .
Corno las rutinas getnode y freenode son muy simples en .esta implantacin, a
Un nodo de este tipo es idntico a los nodos de la implantacin con arreglo, ex- menudo se remplazan por ias instrucciones inmediatas
cepto en que el campo next es un apuntador (que contiene la direccin del siguiente
nodo de la lista) y no un entero (que contiene e!Jndice dentro del arreglo en que se p = ( NODEPTR) malloc ( sizeof ( struct node) ). ;.
guarda el nodo siguiente de la lsta.)
Ahora se usarn las caractersticas de la asignacin dinmica para implantar y.
listas ligadas. En lugar de declarar un arreglo para representar una coleccin global
de nodos, stos se asignan y liberan cuando es necesario. As se elimina la necesidad free(p);
de una coleccin declarada de nodos.
Si. se de.clara A contihuacinse presentan los procedimientos insafter(p, x) y delafter(p, px)
por medio delairnpiantaci..n dinmica de una lista ligada. Supngase que./(st es una
NODEPTR p; variable apuntador que apunta al primer nodo de una lista (si lo hay) y es igual a
NULL en caso de que la lista est vaca.
la ejecucin de la instruccin
insafter(p, x)
p = getnode(); NODEPTR p;
int x;
deber colocar la direccin de un nodo disponible enp. Ahora se presenta la funcin {
getnode: NODEPTR q;
if (p NULL) {
NODEPTR getnode() pin!("inserdn no efectu~da");
{ exit(1);
NO.DEPTR p;
p = (NODEPTR) malloc(sizeof(struct node)); q = getnode();
return(p); q -> info ~ x;
q -> next = p -> next;
p -> next = q.;
Advirtase que sizeof se aplica a un tipo de estructura y regresa el nmero de bytes
/. * fin de insafter * I
requerido para toda la estructura. delafter(p, px)
La ejecucin de la instruccin NODEPTR p;
int *px;
freenode ( p); {

204 Estructuras de datos en C Colas y listas 205


NODEPTR q;
if ((p -- NULL) 11 (p -> next NULL)) insert(pg, x) insert(pq, x)
printf("supresin nula!n"); struct gueue *pq; struct queue *pq;
exit(1); int x; int x;
{ {
q - p -> next; int p; NODEPTR p;
*px = q -> info; p = getnode(); p - ge.tnode ();
p -> next = q -> next; node[pl.info X. p->info = x;
freenode(q); node[pJ.next '
-1,; p->next = NULL;
!* fin de delafter *I if ( pq->rear -- -1) if (pq->rear NULL)
pq->front = p; pq->front - p;
Obsrvese la sorprendente similitud entre las rutinas anteriores y las de la else els8
implantacin con arreglo, las quese presentaron previamente en esta seccin. Am- nde[~q->rearJ.next p; (pq->rear)->next p;
bas son implantaciones de los algodtmos de la seccin 4.2. De hecho, la nica dife- pg->rear = p; pq- >rear - p;
I * fin de nsert * ! l.* -ip. de insert * I
rencia entre las dos versions es la manera en que
. ,,,. '.,. se hce referencia
'' '
a los .nodos
.
..

Colas como listas en C La funcin remove elimina el primer elemento de la cola y de.vuelve su valor:

Para ilustrar con ms claridad la forma en que se usan las implantaciones con remove(pq) remove(pq)
listas en C, se presentan rutinas en C para la manipulacin de una cola representada struct gueue *pq; struct queue *pq
como una lista lineal. A manera de ejercicio, se le dejan al lector las rutinas para ma- { .
{
nipular una pila y una cola. de prioridad. Para propsitos de comparacin se int p, x; NODEPTR p;
muestran tanto la implantacin con arreglo como la dinmica. Puede suponerse que int x
struct nade y NODEPTR fueron declarados igual que arriba. Una cola se representa if (empty(pq)) { if (emptY(pq)) {
como una estructura: printf printf
1 11
( ' subdesborde de la cola\ n ) ("subdesborde de la cola\ n")
exit(1); exit(1);
Impntein con arreglo Implantacin dinmica

struct queue { struct'queue { p - pq->front; p - pq->front;


int front, rear; NODEPTR front, rear; x - node[pl.info; X = p->irifo
}; }; pq->front - node[p].next; pq->fron t = p->next;
struct queue q; struct que.u e q; if (pq->front -- -1) if (pq->front -- NULL)
pq->rear = -1; pq->rear = NULL;
free no de ( p) ; freenode(p);
front y rear son apuntadores al primero y al ltimo nodos de una cola repre- return(x); return(x)
sentada como una lista. font y rear, que igualan al apuntador nulo, representan la I * fin de remove *I I * fin de remove *I
cola vaca. La funcin empty necesita verificar slo uno de esos apuntadores, pues
en una cola no vaca ni front ni rear sern NULL.
Ejemplos de operaciones con listas en C
empty(pq) empty(pq)
struct queue *pq; struct queue *pq.; Enseguida se vern algunas operaciones con lista un poco ms complejas
{ { implantadas en C. Se ha observado que la implantacin dinmica es, por lo general,
return ((pq->front return ((pq-<front mejor que la implantacin con arreglo. Por ello, la mayora de los programadores de
-1) ? TRUE: FHSE); NULL) ? TRUE: FALS C la usan para implantar listas. A partir de este momento habr que apegarse a la
! * fin de empty * I I * fin de empty * / implantacin dinmica de listas ligadas, aunque tambin puede aludirse a la iipln-
tacin con arreglo cuando es apropiado.
La rutina para insertar un elemento dentro de una cola puede escribirse como Ya se defini con anterioridad la operacin p!ace(/ist, x) donde list apunta a
sigue: una lista lineal ordenada y x es un elemento que debe insertarse en la posicin corres-
pondiente de la lista. Hay que recordar que esta operacin se usa para implantar la
206 Estructuras de datos en C
Colas y listas., .. 207
Supngase que ya se int x;
para implantar la operacin {
NODEPTR p;
place es: far (p list; p ! NULL; p p->next)
if (p->info x)
place(plist, x) return (p);
NODEPTR *plist; /* x no est en la lista *I
int x; return (NULL);
{ I * fin de search *I
NODEPTR p, q;
q NULL; La siguiente rutina elimina todos los nodos cuyo campo info contenga el valor x:
far (p. plist; p !~ NULL && x > p->infa P p->next)
q = -p; remvx(plist, x)
=
ii (q = NULL) /* insertar x a la cabeza de la lista- NODEPTR *plist;
push(plist, X); int x;
else {
insafter.(:q, x}; NODEPTR p,'q;
/* fin de place */ int y;
q NULL;
Advirtase que plist deb declararse como un apuntador _al apuntadr de la lista, ya p *plist;
que el valor del apuntador externo a la lista cambia si x se inserta al frente de la mis- while (p ! NULL)
if (p -> info x)
ma por medio de la rutina push. La rutina anterior se llamara por medio de la ins- p =. p->next;
truccin place(&list, x);. if (q NULL)
Como segundo ejemplo, hay que escribir una funcin insend(p/ist, x) para in- /* elmin'ar el primer nodo de la lista *I
sertar el eleme~to x al final de una lista lis/: freenode(*plist);
*plist = p;
insend(plist, x)
NODEPTR *plist; else
int x; delafter(q, &y);
NOD_EPTR p, . q; }
p getnole(); else {
p->info = x; /* avanzar al siguiente nodo de la lista *I
p->next - NULL; q - p;
if (*plist -~ NULL) p p->next;
plist p; I* fin deif *I
else { /* fin de rezriVx *l
/* bsqueda del ltimo nodo * /
far (q *plist; q->next ! NULL; q q->next)

Listas no enteras y no homogneas


q->next = p;
I* fin deif .*!
l * fin de- insend:_ .*l. Por supuesto, un nodo.de una lista no debe-representar por fuerza a un entero ..
Por ejemplo, para representar una pila de cadenas de caracteres mediante una lista
Ahora se presenta una funcin search(list, x) que da como resultado un apuntador a ligada, se necesitan nodos que contengan cadenas de caracteres en sus campos infa,
la primera ocurrencia de x en la lista list y el apuntador NVLL si no existe x en la /!\ .lo.s nodos. de ese tipo, que usan la implantacin de asignacin dinmica, se los
misma: puede declarar por medio de:

NODEPTR search(list, X) struct nade {


NODEPTR list; char info(10Dl;

Estructuras de datos en C C_olas y listas 209


208
9.1 se examinan listas no homogneas, incluyendo listas que contienen otras listas y
struct node *next; listas recursivas.

Comparacin de la implantacin dinmica y con arreglo de listas


Una aplicacin particular puede requerir de nodos que contengan ms de un
elemento de informacin. Por ejemplo, cada nodo de estudiante en una lista de estu- Resulta ilustrativo examinar las ventajas y las desventajas de las implanta-
diantes puede contener la siguiente informacin: nombre del estudiante, nmero de ciones dinmica y con arreglo de listas ligadas. La principal desventaja de la implan-
identificacin del colegio, direccin, ndice de calificaciones y especialidad. Los tacin dinmica es que puede consumir ms tiempo recurrir al sistema para asignar y
nodos para una aplicacin corno sta pueden declararse corno sigue: liberar memoria que manipular una lista disponible manejada por el programador.
Su mayor ventaja es que no se reserva con anticipacin un conjunto de nodos que se-
struct node r usado por un grupo particular de listas.
char name[3DJ; Por ejemplo, supngase que un programa usa dos tipos de listas: listas de ente-
char id[9J; ros y listas de caracteres. En la.implantacin con arreglo se asignaran de inmediato
char address[10DJ; dos arreglos de tamao fijo. Cuando un grupo de listas sobrepasa su arreglo, el
float gpindex; programa no puede proseguir. En la representacin dinmica, dos tipos de nodos se
char major[20J; definen al principio, pero no se asigna memoria a las variables hasta que se necesite.
struct node *next; Cuan:lo esto ocurre, se llama al sistema para que proporcione los nodos. Toda me-
}; moria que no se usa en un tipo de nodo, puede usarse en otro. As, no ocurre desbor-
. de siempre que haya memoria disponible para los nodos presentes de verdad en la
Para manipular listas que contengan cada tipo de nodos, hay que escribir un conjun- lista.
to de rutinas distinto en C. Otraventaja de la implantacin dinmica es que una referencia a *p no implica
Para representar listas no homogneas (aquellas que contienen nodos de tipos el clculo de la direccin necesario para calcular la direccin de node[p]. Para calcu-
diferentes), puede usarse una unin. Por ejemplo, , lar la direccin de node [p], se deben agregar los contenidos de p a la direccin base
del arreglo node, mientras que la direccin de *p est determinada de manera directa
por los contenidos de p.
#define INTGR 1
#define FLT 2 Implantacin de nodos cabecera
#define STRING 3
struct node {
elype ser INTGR, FLT STRING *!
Al final de la seccin anterior se introdujo el concepto de nodos cabecera que
int etype I*
!* dependiendo del tipo del *I pueden contener in formacin global acerca de la lista, tal corno su longitud o un
I* elemento corre~pondiente . *I apuntador al nodo actual o ltimo de la lista. Cuando el tipo de datos de los conteni-
union d.os de la cabecera es idntico al de los contenidos de los nodos de la lista, puede
int ival; implantarse la cabecera slo corno otro nodo al principio de la lista.
float fval; Tambin es posible declarar los nodos cabecera corno variables independientes
char *pval; I* apuntadora una cadena "/ del conjunto de los nodos de la lista. Esto es particularmente.til cuando la informa-
element; cin que contiene la cabecera es diferente a los datos de los nodos de la lista. Por
struct node *next; ejemplo, considrese el siguiente conjunto de declaraciones:
};
struct node
define un nodo cuyos elementos pueden ser nmeros enteros o de punto-flotante o char info;
cadenas, dependiendo del valor correspondiente de etype. Como una unin siempre struct node *next;
};
es lo suficientemente grande para guardar su componente mayor, se pueden usar las
struct charstr
funciones sizeofy mal/oc para asignar memoria al nodo. Por lo tanto, las funciones int length;
getnode y freenode permanecen intactas. Por supuesto, al programador le corres' struct noij~ *firstchar;
ponde usar de manera apropiada los componentes de un nodo. En resumen en el res- };
to de esta seccin puede suponerse que una lista ligada es declarada para que tenga struct charstr s1, s2;
slo elementos homogneos (por lo que las uniones son innecesarias). En la seccin
211
210 Estructuras de datos en C
11
Las variables s I y s2 de tipo charstr son nodos cabecera para una lista de caracteres. corno resultado un apuntador al nodo cabecera de una lista que representa una ca- 1
La cabecera contiene el nmero de caracteres de la lista (fength) y un apuntador a la dena de caracteres, que es la subcadena deseada. La lista /1 permanece intacta.
misma (firstchar). As, si y s2 representan cuerdas de caracteres de longitud va- f. strpsbl(ll, il, i2, /2) para ejecutar una seudoinstruccin substr para una lista /l.
riable. A manera de ejercicio, se pueden escribir rutinas para concatenar dos cade- Los elementos de la lista /2 remplazarn los i2 elementos de /1, comenzando en la
nas de caracteres de este tipo o para extraer una subcadena de tal cadena. posicin il. La lista /2 permanecer intacta.
g. strcmpl(/1, 12) para comparar dos cadenas de caracteres representadas por listas.
Esta funcin da corno resultado -1 si la cadena de caracteres representada por /1
EJERCICIOS
es menor que la representada por /2, O si son iguales y 1 si la representada por /1 es
mayor.
4.3. l. Implante las rutinas empty, push, pop y popandtest por medio de la implantacin con
4.3.10, Escriba una funcin binsrch que acepte dos parmetros: un arregl de apuntadores a
arreglo y la implantacin de memoria dinmica de una pila ligada.
un grupo de nmeros ordenados y un nmero nico. La funcin debe usar la bs-
4.3.2. Implante las rutinas empty, insert y remove mediante una implantacin de memoria queda binaria (vase seccin 3.1) para dar como resultado un apuntador al nmero si
dinmica de una cola ligada. Ste est en el grupo. Cuando esto no es as, regresar el valor NULL.
4.3.3. Irriplante las rl,!tinas empty, pqinsert y pqmindelete mediante una implantacin de 4,3.11. Suponga que se desea formar N listas, donde N es una constante. Declare un arrglo
memoria dinmica de._una cola de prioridad ligada. de apuntadores list mediante:
4.3.4. Escriba-rudnas en C para implantar las operaciones del ejercicio 4.2.3 por medio de
las implantaciones con arreglo y de memoria dinmica de una lista ligada. #define N
4.3.S. Escrib una rutina en C para intercambiar el n-simo y el m-simo elemento de una struct node
lista. int info
4.3.6. Escriba una rutina inssub(/1, 11, /2, i2, tel1) para insertar elementos de la lista /2; co-. struct node *next
menzar con el i2-simo y'continuar para len elementos dentro de la lista /1 empezando };
en la posicin i l. Ningn elemento de la lista /1 debe eliminarse ni remplazarse. Si il typedef struct node *NODEPTR;
> /ength(/1) + .1 (donde /ength(/1) denota el nmero. de nodos de la lista 11) o si i2 + NODEPTR list [Nl;
/en - 1 > Jength(/2), o. si .iJ. < 1, o si i2 < 1, imprime un mensaje de error. La lista
/2 deber permanecer intacta. Lea dos nmeros de cada lnea de entrada, en los que el primero ser el ndice de la lis-
4.3. 7. Escriba una funcin search(l, x) que acepte un apuntador/ a una lista de enteros y un ta .en la que debe colocarse el segundo nmero en ofden ascendente. Cuando no haya
entero x y que d como resultado un apuntador a un nodo que contenga x, en caso de .ms lneas de entrada, imprima todas las listas.
que exista, o en caso contrario el apuntador nulo. Escri~a pt~a funcin, srchinsrt(l,
x), que agregue x a/ si no lo encuentra y que siempre regrese un apuntador al nodo
que contiene x. UN EJEMPLO: SIMULACION POR MEDIO DE LISTAS LIGADAS
4.3.8. Escriba un programa en C para ler un grupo de lneas de epirada, cada una de las.
cuales debe c'ontener una palabra. Imprfma ada palabra que aParezca en raentradaY Una de las aplicaciones ms tiles de las colas, las colas de prioridad y las listas liga-
er nmero de veces que esto o'cur'ra.
d~s es la simulacin. Un programa de simulacin tiene el propsito de modelar una
4.3.9. Suponga qe una. cadena de caracte'r'es se representa mediante una lista de caract~~; situacin del mundo real con el objeto de aprender algo de ella. Cada objeto yac-
res simples. Escriba un conjunto de rutinas para manipular tales listas de la siguiente
cin de la situacin real tiene su contraparte en el programa. Si la simulacin es
manera (en adelante /1, /2 y list son apunt~dores a un nodo cabecera de umi.'lista que
represet'lta Una cadena de crte'res, str es Un arreglo de caracteres, e il e 12 son ente-
exacta, esto es, si el programa refleja con xito el mundo real, el resultado del /:1
ros): programa deber reflejar el resultado de las acciones que se estn simulando. As, es i'
!.J
posible entender qu ocurre en una situacin sin tener que observar en realidad su 1:'
ocurrencia.
a. strcnvcl(str) para convertir la cadena de caracteres str a una lista. Esta funcin da
como resultado un apuntador a un nodo cabecera. Veamos un ejemplo. Suponga que hay un banco con cuatro cajeros. Un cliente
b. strcnvlc(list, str) para convertir una lista a una cadena de caracteres. entra al banco en un instante especfico (ti) y desea hacer una transaccin con algn
c. strpsl(/1, /2) para ejecutar la funcin strpos de la seccin 1.2 sobre dos, cadenas de cajero. Se espera que la transaccin tome cierto tiempo (12) antes de completarse.
caracteres representadas por listas. Esta funcin da corno resultado un entero. Cuando un cajero est libre, procesa la transaccin del cliente de inmediato, el que
d. strvrfyl(II, /2) para determinar la primera posicin de la cadena representada por a.bandonar el banco una vez que se completa la transaccin en el tiempo t 1 .+ 12. El
/1 que no est contenid. en la representada por /2. Esta funcin da como resulta~ tiempo que emple el cliente en el banco es exactamente el mismo que dur la tran-
do un entero. saccin (12).
e. strsbstr(/1, il, i2) para ejecutar la funcin substr de la secCin 1.2 sobre una cadena Sin embargo, es posible que ningn cajero est libre; que todos estn dando
de caracteres representada por la lista /1 y los enteros il, i2. Esta funcin da
servicio a clientes que llegaron antes. En ese caso, en cada ventanilla de cajero hay

212 Estructuras de datos en C


213
una fila de espera. La cola para un cajero en particular puede constar de una sola puertas del banco por ese da, de manera que no entren ms clientes. En tales casos,
persona -la que est realizando una transaccin en ese momento- o pu~de ser una la lista de eventos contiene menos de cinco nodos.
cola muy larga. El cliente se dirige a la cola ms corta y espera a que los clientes ante- A un nodo evento que representa la llegada de un cliente se le llama nodo de
/legada, y a uno que representa la salida, nodo de salida. En cada punto de la simula-
riores completen sus transacciones y abandonen el banco. En ese momento, el clien-
te puede comenzar a tratar sus asuntos. Deja el banco 12 unidades de tiempo despus cin, es necesario saber qu evento ocurrir enseguida. Por esta razn, la lista de
de haber alcanzado el frente de la cola. En este caso el tiempo empleado ser 12 ms eventos se ordena con base en el tiempo ascendente de ocurrencia de un evento para
que el primer nodo de evento de la lista represente el siguiente en ocurrir. As, la lista
el tiempo de espera en la cola. . . . de eventos es una cola de prioridad ascendente representada por una lista ligada
Con semejante sistema, se desea calcular el tiempo promedio nverudo por
cliente en el banco. Una manera de hacerlo es pararse en la puerta del banco Y pre-, ordenada.
guntarle a los clientes. que salen el momento en que llegaron, anotar ~l momento de El primer evento que ocurre es la llegada del primer cliente. La lista de eventos,
su partida y restar el primero del segundo para luego tomar el promedio de todos los por lo tanto, se inicializa mediante la lectura de la primera lnea de entrada y la ubi-
clientes. Sin embargo, esto sera poco prctico. No se podra asegurar si algn clien- .. . cacin de un nodo de llegada, que representa la primera llegada de un cliente a la
te abandon el banco de manera inadvertida. Adems, es dudoso que la mayora de misma. Al principio, por supuesto, las cuatro colas para los cajeros estn vacas. La
simulacin prosigue de la siguiente manera: se elimina el primer nodo de la lista de
los clientes recuerde el tiempo exacto de su llegada.
En lugar de ello, se debe escribir un programa para simular las acciones de los eventos y se hacen los cambios que dicho evento ocasiona en las colas. Como se ver
clientes. Cada parte de la situacin del mundo real tiene su parte anl?ga en el delante, esos cambios tambin pueden causar eventos adicionales que deben colo-
programa. La accin del mundo real de un cliente que llega se model~ mediante una , tarse en la lista de eventos. El proceso de eliminacin del primer nodo de la lista de
entrada de datos. Cada que llega un cliente se conocen dos cosas: el t1empo de llega- 0 ' eventos y la actualizacin de los cambios que eso conlleva, se repite hasta que la lista
da y la duracin de la transaccin (pues, supuestamente, todo client_e que lleg~ sabe,} de eventos est vaca.
lo que desea hacer en el banco). As, la entrada de datos para cada chente consiste e~ Cuando se elimina un nodo de llegada de la lista de eventos, se coloca un nodo
un par de nmeros: el tiempo (en minutos desde que abre el banco) en que llega eL. . .que representa al cliente. que llega a la fila ms corta. Si ese cliente es el nico en
cliente y el tiempo (de nuevo en minutos) necesario para la transacci?. Los pares de, una cola, en la lista de eventos se coloca tambin un nodo que represente la salida del
datos se ordenan de acuerdo con el tiempo de llegada ascendente. As1, se asume por. 'cliente, ya que ste est en el .frente de la cola. Al mismo tiempo, se lee la lnea
de entrada siguiente y en la lista de eventos se coloca un nodo de llegada que represente
lo menos una lnea de entrada. . ...:'
Las cuatro filas del banco se representan por medio de cuatro colas. Cadi al siguiente cliente que llega. En la lista de eventos siempre habr slo un nodo de
nodo de la cola representa un cliente que espera en una fila, Y el nodo del frente llegada (siempre que no se agote la entrada, momento en el que ya no llegan ms
"dientes), ya que tan pronto como se elimina un nodo de llegada de la lista de even-
representa el cliente que es atendido por un cajero. . . .,(
Supngase que en un momento determinado las cuatro filas contienen un n~- tos, se agrega otro a la misma.
mero especfico de clientes. Qu debe ocurrir para que se altere el estado de las f\ Cuando se elimina un nodo de salida de la lista de eventos, el nodo que repre-
las? Ya se_a que un nuevo cliente entre al banco, en cuyo caso una de las filas ten~r \senta al cliente que se va, se elimina del frente de una de las cuatro colas. En ese
un cliente adicional, o que el primer cliente de una de las filas complete su transas >'momento, se calcula la cantidad de tiempo que emple ese cliente en el banco y se
cin, en cuyo caso la fila tendr uno menos. As, hay un total de cinco accione~ (u agrega al total. Al final de la simulacin se divide ese total entre el nmero de clien-
cliente que entra, ms cuatro posibilidades de que uno salga) que pueden cambiar tes para obtener el tiempo_ promedio empleado por cada uno. Una vez que se borra
estado de las filas. Cada una de esas cinco ..acciones se denomina como evento. .un nodo que representa a un cliente del frente de la cola, el siguiente cliente (si lo
1
hay) avanza para recibir el servicio del cajero correspondiente y se agrega a la lista de
.ventos un nodo de salida para el siguiente. 1
El proceso de simulacin 1
.... Este proceso contina hasta que la lista de eventos est vaca, momento en el
La simulacin se realiza encontrando el siguiente evento y efectuando el cam :~1 se calcula el tiempo promedio y se imprime. Advirtase que, por s misma, la lis-
bio en las colas que refleje el cambio ocasionado por dicho evento en las filas d~ de ventos no refleja ninguna circunstancia del mundo real. Se usa como parte del
banco. Para mantenerse informado de los eventos, el programa usa una col~ ~ .. ograma para controlar todo el proceso. Una sirnuacin como sta, que acta
prioridad ascendente, llamad.a lista de eventos. Esta lista contiene a lo sumo c~n,c .ffibiando la situacin simulada en respuesta a la aparicin de uno de varios even-
nodos, cada uno de los cuales representa la siguiente ocurrencia de uno de l?s ~1~. ..5 se conoce como simulacin conducida por eventos.
tipos de evento. As, la lista de eventos contiene un nodo que representa al s1gmen
cliente que llega y cuatro nodos que representan a cada uno de los cuatro clientes, Estructuras de datos
la cabeza de una fila, que terminan su transaccin y abandonan el banco. Porsw. Ahora se examinarn las estructuras de datos necesarias para este programa.
puesto, es posible que una o ms lneas del banco estn vacas, o que se cierren l,~ .s nodos de las colas representan clientes y deben, en consecuencia, tener campos

Estructuras de datos e 215


214
que representen el tiempo de llegada y la duracin de la transaccin, adems del El programa de simulacin
campo next para ligar los nodos de una lista. Los nodos de la lista de eventos repre-
sentan eventos y, por lo tanto, deben contener el tiempo en que ocurre el evento, el La rutina principal inicializa todas las listas y colas y elimina repetidamente el
tipo de evento y cualquier tipo de informacin relacionada con el mismo, as como nodo siguiente de la lista de eventos para conducir la simulacin hasta que la lista de
un campo next. As, parecera que se necesitan dos depsitos de nodos diferentes pa- eventos est vaca. La lista de eventos se ordena incrementando los valores del cam-
ra los dos tipos de nodos diferentes. Dos tipos de nodos diferentes ocasionaran dos po time. El programa llama a place(&evlist, &auxinfo) para insertar un nodo cuya
rutinas getnode y freenode y dos conjuntos de rutinas diferentes para la manipula- 'informacin est determinada por auxinfo en el lugar apropiado de la lista de even-
cin de listas. Para evitar este molesto conjunto de rutinas duplicadas, se tratar de tos. La rutina principal llama tambin a popsub(&evlist, &auxinfo) para eliminar el
usar un solo tipo de nodo tanto para eventos como para clientes. primer nodo de la lista de eventos y colocar su informacin en auxinfo. Esta rutina
Se puede declarar un depsito para los nodos y un tipo apuntador como sigue: es equivalente a la funcin pop. Estas rutinas deben, por supuesto, modificarse de
manera apropiada, a partir de los ejemplos proporcionados en la ltima seccin, pa-
struct node ra poder tratar este tipo de nodo particular. Advirtase que ev/ist, place y popsub no
int time; son ms que una implantacin particular de una cola de prioridad ascendente y de
int duration
las operaciones pqinsert y pqmindelete. Una representacin ms eficiente de una
int type;
cola de prioridad (como la que se presenta en las secciones 6.3 y 7.3) permitiran que
struct node *next;
}; el programa operase con fris eficacia.
typedef struct node *NODEPTR; El programa principal llama tambin a las funciones arrive y depart, las que
efectan los cambios causados por una llegaday una salida en las colas y la lista de
En un nodo para cliente, .time es el tiempo de llegada del cliente y duration es la eventos. En especial, la funcin arrive(atime, dur) refleja la llegada de un cliente
duracin de la transaccin. type no se usa en los nodos para clientes. next es un en el tiempo atime con una transaccin de duracin dur, y la funcin depart(qindx,
apuntador para ligar los elementos de la cola. En los nodos para eventos se usa time dtime) refleja la salida del primer cliente de la cola q[qindx] en el tiempo dtime. Los
para guardar el tiempo de ocurrencia del evento; duration se usa para la duracin de cdigos de estas rutinas se proporcionarn en breve.
la transaccin de un cliente que llega en un nodo de llegada, pero no se usa en los no-
dos de partida. type es un entero entre -1 y 3,. dependiendo de si el evento es una lle- #include <STDIO.H>
gada (type = = -1) o una salida de la lnea O, 1, 2 o 3 (type = = O, 1, 2, o 3). next #define NULL o
guarda un apuntador que liga la lista de eventos. struct nade {
Las cuatro colas que representan las filas para los cajeros se declaran como un int duration, time, type;
arreglo mediante la declaracin: struct node *next;
};
struct queue typedef struct node *NODEPTR;
NDEPTR front, rear struct queue {
int num NODEPTR front, rear;
}; int num;
struct queue q[J; };
struct gueue q(~J; ,1
La variable q[i] representa una cabecera para la i-sima cola de cajero. El campo structnode aniinfo;
NODEPTR evlist;
num de una cola contiene el nmero de clientes de la misma.
in{ atime, dtime, dur, qindx;
Una variable evlist apunta al frente de la lista de eventos. Una variable tottime
float count; tottime;
se usa para mantenerse informado sobre el tiempo total empleado por todos los
clientes y count cuenta el nl]1ero de clientes que han pasado por el banco. Esta, main ()
variables se usarn al final de la simulacin para calcular el tiempo promedio {
empleado por los clientes del banco. Para almacenar por un tiempo la porcin de in- /* inicializaciones */
formacin de un nodo se usa una variable auxiliar, auxinjo. Estas variables se decla- evlist - NULL;
ran por medio de count = Q;
tottime = O;
NODEPTR evlist; for (qindx = o; qindx < ~; qindx++)
float count, tottime; q[qindx}.num = D;
struct node auxinfo;

216 Estructuras de datos en C Colas y listas 217


q[qindxl.front = NULL; j = O
q[qindx].rear = NULL; small = q[O].num;
/ * fin de for * / fer ( i = 1; i < '; i++)
/* inicializar la lista de eventos con la primera llegada if (q[i].num < small)
printf(''proporcionar tiempo y duracin\ n") small = g(iJ.num;
scanf( 11 %d %d 11 , &auxinfo.time, &auxinfo.duration); j = i;
auxinfo.type = -1; I* una llegada *I I* findefor ... if *I
place(&evlist, &auxinfo) /* La cola j es la ms corta. Insertar un nuevo nodo de cliente . */
auxinfo.time = atime;
I * Efectuar la simulacin mientras */ auxinfo.duration = dur;
! * la lista de eventos no se vace */ auxinfo.type = j;
while (evlist != NULL) insert(&q[jl, &auxinfo);
popsub{&evlist, &auxin_fo); I* Verificar si es el nico nodo en la cola. De ;
! * verificar si el siguiente evento */ I* ser as, el nodo de slida del cliente d~ber *I
I* es una llegada o una s~lda */ /* ca1ocdrse en la lista de eventos *I
if (auxinfo.type == -1) { if (1[jl.num == 1) {
I* una llegada */ auxinfo.time = atime + dur
atime = auxinfo.time; place(&evlist, &auxinfo); '
dur = auxinfo.duration;
arrive(atime, dur) I* Si an existen algunos datos de entrada, leer el siguiente */
J I~ par de datos Y colocar una llegada en la lista de eventos. *I
else { pnntf( 11proporcionar tiempo \n");
J * una salida *I if (scanf("%d",&auxinfo.time) ! ~ EOF)
qindx = auxinfo.type; printf("indique la duracin \n");
dtime = auxinfo. time; sca~f(' 1 %d 11 , &auxinfo.duration);
depart/ qindx 1 dtime) auxinfo.type = -1;
!* fin de if *I place(&evlist, &auxinfo);
! * fin de while *! I* fin de if *!
printf("El tiempo promedio es %4.2f", tottime/count); I* , fin de arrive *;
!* fn de main *I

La r?tina depart(qindx, dtime) modifica la cola q[qindx] y la lista de eventos


La rutina arrive(atime, dur) modifica las colas y la lista de eventos para refle,
p~ra. refleJar la salida del primer cliente de la cola en el tiempo dtime. El cliente se
jar una nueva llegada en el tiempo atime. con una transaccin de duracin dur. Esta
~[11:1ma de la cola con una Uamada a remove(&q[qindx], &auxinfo ), la que debe mo-
rutina inserta un nuevo nodo al final de la cola ms corta mediante una llamada a la
dificarse ~e :anera adecuada para tratar el tipo de nodo de este ejemplo y tambin
funcin insert(&qUJ, &auxinfo). La rutina insert debe modificarse de manera apro-
d,be ~1smm?rrse en 1 el campo num de la cola. El nodo de salida del siguiente cliente
piada para tratar el tipo de nodo de este ejerriplo y tambin debe incrementarse en
9ela cola (s1 lo hay) remplaza al nodo de salida que apenas se elimin de la lista de
1 qUJ.num. Cuando un cliente es el nico en la cola, se agrega un nodo que represen- ~yentos.
te su salida o llegada a la lista de eventos por medio de la funcin place(&evlist/l ;--t
&auxinfo ). Despus se lee el par de datos siguiente (si existe) y se coloca un nodo de. I''
llegada en la lista de eventos para remplazar la llegada que se acaba de procesar. Si depart(qindx, dtime)
int qindx, dtime;
no hay ms entradas, la funcin regresa sin agregar un nuevo nodo de llegada y e[ {
programa procesa los nodos (de salida) restantes en la lista de eventos. NODEPTR p;
remove(&q[qindxJ, &auxinf0);
arrive(atime, dur) tottime = tottime + (dtime - auxinfo.time);
int atime, dur; count++;
1 I* Si hay algunos otros clientes en la cola, colocar *I
int i, j, small;
I* la salida del siguiente cliente en la lista de *I
I* eventos despus-de calcular su tiempo de partida
!* localizar la cola ms corta */ *I

218 Estructuras de datos en . 219


if (q[qindxl.num > O) 4.4.7. Qu partes del programa de la simulacin del banco seran transformadas si la cola de
p g[gindxl.front; prioridad para los eventos se implantara como un arreglo o una lista no ordenada?
auxinfo.time = dtirne + p->duration; Cmo podran modificarse?
auxinfo.type = qindx; 4.4.8. Muchas simulaciones no simulan eventos proporcionados por datos de entrada, sino
place(&evlist, &auxinfo); por eventos generados de acuerdo con cierta distribucin de probabilidad. Los siguien-
!* fin de if *f tes ejercicios explican cmo. La mayora de las computadoras tienen una funcin
! * fin de depart * I rand(x) que genera un nmero aleatorio. (El nombre y los parmetros de la funcin
varan de un sistema a otro. rand se usa slo como ejemplo.) x se inicializa a un valor
llamado semilla. La instruccin x == rand(x) reinicializa el valor de la variable x a un
Los programas de simulacin son variados en el uso de estructuras deUsta. S_e
nmero real aleatorio uniforme entre O y l. Esto quiere decir que s se ejecuta la ins-
invita al lector a explorar el uso de C para la simulacin y el uso de lenguaJes de si- truccin un nmero suficiente de veces y se eligen dos intervalos de longitud igual entre
mulacin de propsito especial. O Y 1, caern en ellos la misma cantidad de valores sucesivos de x de manera aproxima~
da. As, la Probabilidad de que un valor de x caiga en un intervalo de longitud/ < = t
es igual a /. Encuentre el nombre de la funcin que genera nmeros aleatorios en el
EJERCICIOS sistema y verifique que lo siguiente sea cierto.
Dado un generador, de nmeros aleatorios rand, considrese las siguientes instruc-
4.4.1. En el programa del texto de simulacin del banco, un nodo de salida. de la lista de e~en- ciones:
tos representa al mismo cliente del primer nodo de una cola de c!r~ntes. Es posible
usar un solo nodo para un cliente que recibe servicio? Volver a escnb1r el programa del x rand(x);
texto de manera que se us un solo nodo. Existe alguna ventaja si. se emplean dos y (b-a)*x + a
nodos?
4.4.2. El programa del texto usa el mismo tipo de nodos tanto para clientes como para even- Muestre que, si se dan dos intervalos cualquiera de igual longitud dentro del intervalo de
tos. Escribir de nuevo elprograma usando dos tipos de nodos diferentes para esos dos a a b Y si las instrucciones se repiten con suficiente frecuencia, el mismo nmero de va-
propsitos. Esto ahorra espacio? lores sucesivos de y cae en cada uno de !os intervalos en forma aproximada. Muestre
4.4.3. Revise el programa de simulacin del banco, presentado en el texto, para determinar la que si a y b son enteros, la parte entera de los valores sucesivos de y es igual a cada ente-
longitud promedio de las cuatro filas. ro entre a Y b - 1 en un nmero de veces aproximadamente igual. Se dice que la
4.4.4. Modifique el programa de simula~_in del banco para calcular las desviacione~ estndar variable y es una variable aleatoria distribuida uniformemente. Cul es el promedio de
del tiempo que invierte un cliente en el banco. Escriba otro, programa que s1mul~ una los valores de y en trminos de a y b?
sola cola para los cuatro cajeros, en el que el cliente que esta a la cabeza de una fila va Reescriba la simulacin del banco que se presei:it en el texto suponiendo que la dura-
al sigui.ente cajero disponible. Compare la media Y. la desviacin estndar de los dos cin de la transaccin est distribuida de manera uniforme entre 1 y 15. Cada par de
mtodos. , datos representa un cliente que llega y slo contiene el tiempo de llegada. Al leer una
4.4.5. Modifi(lue el programa de s.imulacin del banco de manera que siempre que la longitud lnea de entrada, genere una duracin para la transaccin del cliente calculando el valor
de una de las filas exceda a lffde otra en ms de dos, el ltimo cliente de la fila ms larga sucesivo de _acuerdo con el mtodo que se acaba de esbozar.
pase al final de la ms corta. 4.4.9. Se dice que los valores sucesivos de y, generados por las siguientes instrucciones, estn
4.4.6. Escriba un programa en C para s.imular un si$,tema de Computadoras de usuar_ios ml~ normalmente distribuidos. (En realidad, casi estn normalmente distribuidos, pero la
tipes l.e la siguiente manera: cada usuario tiene una ID {identificacin) nica Y desea aoroximacin es realmente buena.) :11
realizar un nmero de transacciones en la computadora. Sin embargo, en un momento
dado la computadora slo puede procesar una transaccin. Cada lnea de entrada float x[15l;
repre~enta un solo usuario y contiene el ID de ste seguido del tiempo de co.mienzo Y float m, s, sum, y;
una serie de enteros que representan la duracin de cada una de las transaccwnes. La int i;
entrada se clasifica de manera creciente de acuerdo con el tiempo de inicio, Ytqdas las I* A(Iu se colocan las instrucCones para dar los *I
d~raciones y los tiempos estn proporcionados en segundos. Supngase. que c~? I* valores iniciales de s, m y el arreglo x *I
usuario solicita tiempo para una transaccin hasta que se ha completado la ttansacc10n While I* aqu se ubica la condicin de terminacin *! )
anterior y que la computadora procesa transacciones con la polt.ica de el_ prin:ier_o en sum = D;
llegar, el primero en ser atendido. El programa deber simular el s1_stema e 1mp~1m1r un for (i ~ D; i < 15; i++)
mensaje que contenga la ID del usuario y el tiempo en que comienza Y tennma una x[il rand(x[il);
transaccin. Al final de la simulacin d~be imprimir el tiempo promedio de espera para sum = sum + x(iJ;
una transaccin. (El tiempo de espera es.la cantidad del tiempo desde que se solicita la I* end for *I
transaccin hasta que sta comienza.) y s * (sum - 7.5) ! sgrt(1.25) + rn;

Estructuras de datos en C ,
220 221
lista
/* Aqu se colocan las instrucciones que emplean el valor de y *I
I* fin de while * I Primer Ultimo
nodo nodo
Verifique que el promedio de los valores de y (la media de la distribucin) es igual a m y
que la desviacin estndar es igual a s.
Cierta fbrica produce artculos de acuerdo con el siguiente proceso: se debe ensamblar
y pulir un artculo. El tiempo de ensamblaje se distribuye de manera uniforme entre Figura 4.5.2 Primero y ltimo nodo de una lista circular.
100 y 300 segundos, y el de pulido con una media de 20 segundos y una desviacin es-
tndar de 7 segundos (pero se descartan los valores por debajo de 5). Despus de que se
ensambla un artculo, hay que usar una pulidora y un trabajador no puede comenzar a eliminar elementos de manera adecuada, ya sea desde el frente o desde el final de la
ensamblar el artculo siguiente hasta que no est pulido el que acaba de montar. Hay lista. Tambin se establece la convencin de que el apuntador nulo representa una
diez trabajadores, pero una sola pulidora. Si sta no est disponible, los trabajadores _lista circular vaca.
que han terminado de ensamblar sus artculos deben esperar. Calcule el tiempo prome-
dio de espera por artculo mediante una simulacin. Haga lo mismo suponiendo que La pila como una lista circular
hay dos o tres mquinas pulidoras.
Una lista circular puede usarse para representar una pila o cola. Sea stack un
apuntador al ltimo nodo de una lista circular, y adptese la convencin de que el
4.5. OTRAS ESTRUCTURAS DE LISTA primer nodo es el tope de la pila. Una pila vaca se representa mediante la lista nula.
La siguiente es una funcin en C para determinar si la pila est vaca. A
Aunque una lista lineal ligad es una estructura de datos til, tiene varios defectos.
empty(&stack) llama a esta funcin.
En esta seccin se presentan otros mtodos para organizar una lista y se muestra
cmo usar stos para superar dichos defectos. empty(pstack)
NODEPTR *pstack;
Listas circulares {
return ((*pstack NULL) ? TRUE FALSE);
Luego de que determina un apuntador p a un nodo en una lista lineal, resulta I * fin de em.pty * !
imposible alcanzar algn nodo que preceda a node(p). Si se recorre una lista, hay
que preservar el apuntador externo a la lista para poder referenciar sta de nuevo, . : ... ~a siguiente es una funcin en C para poner un entero x dentro de una pila. La
Suponga que se hace un pequeo cambio a la estructura de una lista lineal de func1on push llama a una: funcin empty, la que verifica si su parmetro es NULL.
manera que el campo next en el ltimo nodo contenga un apuntador al primer nodo LtUamada se realiza mediante push(&stack, x), donde stack es un apuntador a una
en lugar del.apuntador nulo. Dicha lista se llama lista circular y se ilustra en la figura li~la c_ircular que acta como pila.
4.5.1. En una lista como sa es posible alcanzar cualquier punto de la lista desde otro
cualquiera. Si se empieza en un
nodo determinado y se recorre toda la lista, se termi' push(pstack, x)
nar al final en el punto de partida. NODEPTR *pstack;
Obsrvese que una lista circlar no tieneun "primer" o "ltimo" nodo natu- int x;
ral. Por lo tanto, hay que establecer un primer y ltimo nodo por convencin. Una {
convencin til es dejar el apuntador externo a la lista circular apuntando al ltimo NODEPTR p;
nodo y dejar que el nodo siguiente sea el primero, como se ilustra en la figura 4.5.2 .. p = getnode();
Si pes un apuntador externo a una lista circular, esta convencin permite accesar.el.i p->info = x;
ltimo nodo de la lista referenciando a node(p) y el primer nodo de la lista referen- if (empty(pstack) TRUE)
ciando node(next(p)). Esta convencin proporciona la ventaja de poder agregar.o *pstack p;
else
p->next (*pstack) -> next;

ci~~+-1
(*pstack) -> next = p;
} /* fin de push */
Advirtase que la rutina push es algo ms compleja para las listas circulares que para
Figura 4.5.1 Una lista circular. las listas lineales.

Estructuras de datos en O 223


222
*pq = p;
La funcin pop en C para una pila implantada como lista circular llama a la
return;
funcin Jreenode presentada con anterioridad. pop es llamada por pop(&stack). } /* fin de inserf *I
pop(pstack) Advirtase que insert(&q, x) es equivalente al cdigo:
NODEPTR *pstack;
1 push(&q,x);
int x;
NODEPTR p; q = q->next
if (empty(pstack) TRUE)
printf("subdesborde de la pila\ n"); Bsto es, para insertar un elemento al final d.e una cola circular, se debe insertar ste
exit(1); en el frente de la cola y el apuntador a la lista circular debe recorrerse un elemento
/* findeif */ hacia delante, por lo que el nuevo elemento se convierte en el del final.
p (*pstack) -> next;
x = p->info;
if (p pstack) Operaciones primitivas en listas circulares
/* slo un nodo en la pila --
*pstack = NULL; La rutina insaftef(p, x), que inserta un nodo que contiene x despus
else denode(p), es similar a la rutina correspondiente para listas lineales presentadas en
(*pstack) -> next p->next; la'se~cin ~.3. Sin e~bargo, es necesario modificar ligeramente la rutina delafter(p,
freenode(p); x). S1. se mira la rutma correspondiente para listas Jrnealcs presenta.da en la seccin
return(x);
4.3, debe advertirse una consideracin adicional en e: cso de una lista circular. Su-
) I* fin de pop *I
pngase que p apunta al nico nodo de la lista. En una lista lineal, next(p) sera nulo
en este caso, lo que invalida la eliminacin. Sin embargo, en una lista circular,
next(p) apunta a node(p ), de tal manera que node(p) se sigue a s mismo. La pregunta
La cola como una lista circular
en este caso, es si es deseable o no eliminar node(p) de la lista. Es improbable que se
Es ms fcil representar una cola como una lista circular que como una lineal. desee esto, ya que la operacin delafter se invoca de manera normal cuando los
Como lista lineal, una cola se especifica por medio de dos apuntadores, uno al frente apuntadores d~ l~s dos nodos estn detercninados de manera que uno sigue al otro y
de la lista y otro al final. Sin embargo, mediante una lista circular, se puede especifi- que se desee ehmmar el segundo. Para listas circulares, delafter se implanta median-
car una cola a travs de un apuntador simple q a dicha lista. node(q) es el final de la te la implantacin dinmica con nodos como sigue:
cola y el siguiente nodo es su frente.
La funcin empty es la misma que para pilas. La rutina remove(pq) llamada delafter(p, px)
por remove(&q) es idntica a pop, excepto en que todas las referencias a pstack son NODEPTR p;
remplazadas por pq, un apuntador a q. La rutina C insert se llama por la instruccin int *px; i
1
insert(&q, x) y se puede codificar como sigue: 1
NODEPTR q;
if ((p -- NULL) 11 (P -- p->next)) {
insert(pq, x) I * la li'S'te bt vaca o contiene un solo n~do *I
NODEPTR *pq; printf("eliminacin ,:-, : ... -actuada\ n ");
int x; return
1 I* fin deif *!
NODEPTR p;
q p->next;
p getnode();
*px = q->info;
p->info = x;
p->next = q->next;
if (empty(pq) TRUE)
freenode(q)
*pq - p; return; ...
else I * fin de delafter */
p->next = (*pq) -> next;
(*pq) -> next - p;

Estructuras de datos en 225


224
Obsrvese, sin embargo, que insafter no puede usarse para insertar un nodo ranza de victoria sin refuerzos, y slo hay un caballo disponible para el escape. Los
que siga al ltimo de una lista circular y que delafter no se pu,:de usar para eliminar soldados estn de acuerdo en pactar para determinar cul de ellos debe escapar y pe-
el ltimo nodo de una lista circular. En ambos casos, el apuntador externo a la lista dir ayuda. Forman un crculo y se escoge un nmero n de un sombrero. Tambin se
debe modificarse para apuntar al nuevo ltimo nodo. Es posible modificar rutinas escoge uno de sus nombres de un sombrero. Se empieza a contar en el sentido de las
para aceptar /ist como parmetro adicional y cambiar su valor cuando sea necesario. manecillas del reloj empezando por el soldado cuyo nombre fue elegido alrededor
(El parmetro actual en la rutina de llamada tendra que ser &lis/, ya que su valor se del crculo. Cuando el conteo llega a n, ese soldado se elimina del crculo y el conteo
cambi.) Una alternativa es escribir rutinas distintas insend y dellas/ para esos casos. ,comienza de nuevo con el soldado siguiente. El proceso contina de modo que cada
(insend es idntica a la operacin insert para una cola implementada como una lista vez que el conteo alcanza a n se elimina un soldado del crculo. Ningn soldado eli-
circular.) La rutina de llamada sera responsable de determinar qu rutina llamar. minado del circulo se vuelve a contar. El ltimo que queda toma el caballo y escapa.
Otra posibilidad es dar a la rutina de llamada la responsabilidad de ajustar el apun- Dado un nmero n, el orden de los soldados, en el crculo y el soldado a partir del
tador externo lis/ si es necesario. El anlisis de esas posibilidades se le quedan al cual comienza el conteo, el problema consiste en el orden en el que se eliminarn los
lector. soldados del crculo y el soldado que escapa.
Si se maneja una lista propia de nodos disponibles (por ejemplo, al usar la La entrada del programa es el nmero n y una lista de nombres, que es el orden
implantacin por arreglos), tambin es ms fcil liberar una lista circular completa en el sentido de las manecillas del reloj de los soldados del crculo, comenzando por
que liberar una lista lineal. En el caso de una lista lineal, sta deber recorrerse por el soldado a partir del cual comienza el conteo. La ltima lnea de entrada contiene
completo, ya que los nodos se colocan, uno a uno, en la lista disponible. En el caso la cadena "END", que indica el final de la entrada. El programa deber imprimir
de una lista circular, se puede escribir una rutinafreelisl que libere de manera efecti- los nombres en el orden en que se eliminan del crculo y 'el del soldado que escapa.
va una lista completa con el simple hecho de volver a arreglar los apuntadores. Esto, Por ejemplo, supngase que n ~ 3 y que hay cinco soldados llamados A, B, C,
se deja como ejercicio para el lector. D y E. Hay que contar tres soldados a partir de A, de manera que Ces el primero en
De manera anloga, se puede escribir una rutina concat(&/ist 1, &lis/2) que ser eliminado. Luego se comienza con D, se cuenta D, E y A, y se elimina A. Des-
concatene dos listas, esto es, que agregue la lista circular apuntada por list2 al final pus se cuenta B, D, y E (C ya se elimin) y por ltimo B, D y B, de tal manera que
de la apuntada por list l. Si se usan listas circulares, esto puede hacerse sin recorrer es D el que escapa.
ninguna de las listas: Desde luego que en la resolucin ,de este problema es natural usar una lista cir-
cular en la que cada nodo represente un soldado como estructura de datos. Es po-
sible alcanzar cualquier nodo a partir de otro si se cuenta alrededor del crculo. Para
concat(plist1, plist2)
NODEPTR *plist1, *plist2; representar la eliminacin de un soldado del crculo, se borra un nodo de la lista
{ circular. Al final, cuando slo resta un nodo en la lista, el resultado est determinado.
NODEPTR p; Un esbozo del programa podra ser el siguiente:
if ( plist2 NULL)
return; read(n);
if (*plist1 NULL) read(name);
*plist1 = *plist2; while (name != END)
return; insertar el nmbre en la lista circular
read(name);
p = (*plist1) -> next; } / fin de while * I
(*plist1) -> next = (*plist2) -> next; while (Existe ms de ~un nodo en la lista)
(*plist2) -> next = p; contar .ri~l nodos en la lista
*plist1 = *plist2; imprimir el nombre del n~simo nodo
return; eliminar el n~simo nodo;
I * fin de concat * I } / * fin de while * I
imprimir el nombre del nico nodo en la lista;

El problema de Josephus Supngase que se ha declarado un conjunto de nodos igual que el anterior, excep-
to en que el campo info contiene una cadena de caracteres (un arreglo de caracteres)
Considrese un problema que puede resolverse de manera directa mediante .en lugar de un entero. Supngase tambin por lo menos un nombre en la entrada. El
una lista circular. El problema se conoce como el problema de Josephus y postula a programa usa las rutinas insert, de/after y Jreenode. Las rutinas insert y delafter se
un grupo de soldados rodeados por una abrumadora fuerza enemiga. No hay espe- e.deben modificar, ya que la porcin de informacin del nodo es una cadena de carac-

226 Estructuras de datos en C 227


teres. La asignacin de una cadena de caracteres variable a otra se realiza mediante
un ciclo. El programa tambin se vale de una funcin eqstr(str 1, str2), la que da co-
mo resultado TRUE si strl es idntica a str2 y FALSE en caso contrario. La codifi,
cacin de dicha rutina se deja como ejercicio al lector.

josephus ()
1 Figura 4.5.3 Una lista circular con un nodo cabecera.
char *end = 11 ena 11 ;
char narne(MAXLEN];
int i, n; ciaLen su campo info, que no puede ser un contenido vlido como nodo de la lista en
NODEPTR 11st NULL; .el contexto del problema, o P.ede contener un indicador que la marque como cabe-
printif(proporcionar n/n"); cera. La lista se puede recorrer por medio de un solo apuntador, deteniendo el
scanf( 11 %d' 1 , &n); recorrido al encontrar el nodo cabecera. El apuntador externo a laUsta apunta al nodo
I* leer los,nomb'res, 'colocndolos *'! cabecera, como se muestra. en la figura 4.5.3. Esto significa que no es muy fcil agre-
I * al final de la lista *I gar un nod.o al final de una(lista circular como se podra hacer si el. apuntador exter-
nintf("Proporcione los nombres\ n");.
no apuntara .al ltimo nodo de la misma. Por supuesto, es posible mantener un
scanf("%s", name);
apuntador al ltimo nodo de la lista circular aun cuando se use un nodo cabecera.
/* construccin de la lista */
while (!eqstr.(name, _end}) ..
Cuando se usa un apuntador externo estacionario a. una lista circular, adems
i~s~rt(&list, na~e); del apuntador para el recorrido, no es necesario que el nodo cabecera contenga un
scanf( 11 %s", na-me); cdigo especial aunque se pueda usar casi de la misma manera que un nodo.cabecera
I* fi!l de whHe. *I de una lista lineal para contener informacin global acerca de la lista. El final del re-
printf corrido se sealara mediante la igualdad del apuntador del recorrido y el apuntador
("El orden en que se elm'inan los soldados eS n") estacionario externo.
I* Continuar contan9-o'.mentras_exista *I
I* ! ms de un nodo en la lista *I Suma de enteros positivos largos mediante listas circulares
while (11st ! list->next) 1
for ( i = 1; i < n; i++)
Ahora se presentar una aplicacin de las listas circulares con nodos cabecera.
list = list->next;
I* list->next apunta al n-simo nodo *I
El hardware de muchas computadoras slo permite enteros de una longitud mxima
delaft'er(list, name); especfica. Supngase que se desea representar enteros positivos de longitud arbitra-
printf( 11 %s\n, name); ria y escribir una funcin que d como resultado la. suma de dos de esos enteros.
/ * fin de while * I Para sumar dos enteros de ese tipo se recorren sus dgitos de derecha a izquier-
J* imprimir el nico nombre en la lista y liberar su nodo y se agregan los dgitos correspondientes, as como el posible acarreo de la suma
printf("el soldado que escapa es: %S",1ist - > info); freenode(list); de los dgitos previos. Esto sugiere la representacin d enteros largos mediante el al-
freenode( 11st); macenamiento de sus dgitos, de derecha a izquierda, en una lista para que el primer
! * fin de josephus *! nodo de la lista contenga el dgito menos significativo (el de la extrema derecha), y el
ltimo, el ms significativo (el de la extrema izquierda). Sin embargo, para ahorrar
epacio, se guardan cinco dgitos en cada nodo. (Se usan variables Qe enteros largos
Nodos cabecera para poder guardar en cada nodo nmeros tan grandes como 99999. El tama'io m-
ximo de un entero depende de la implantacin; en consecuencia,.tendran que modi-
Supngase que se desea recorrer una lista circular. Esto puede hacerse ejecu- ficarse las rutinas para guardar nmeros ms pequeos en cada nodo). El conjunto '"
tando c forma repetida p = p - > next, donde p es en el inicio un apuntador al de nodos se puede declarar mediante:
principio de la lista. Sin embargo, como la lista es circular, no se puede saber cundo
Strrict node
se ha recorrido toda la lista sin que otro apuntador, lis/, apunte al primer nodo de la
long int info;
lista y sin que se verifique la condicin p = = /ist. struct node *next;
Un mtodo alterno seria colocar un nodo cabecera como el.primer nodo de una' ) ;
lista circular. Esta cabecera de lista puede reconocerse por medio de un valor espe"' typedef struct nade *NODEPTR;

228 Estructuras de datos en(!)


229
lista s s->next
/ p p-.>next.;

el -\ i +-1984631 +- 459 -'--~


' - - - - 1
q
I*
q->next;
determinar si existe acarreo
carry = total / hunthou;
*I

I* fin de while *I
Figura 4.5.4 Un entero grande como una lista circular. I* a partir de aqu, pueden quedar todava nodos en alguna de *I
I* las listas de entrada *!
while (p->info != -1) {
Ya que se desea recorrer las listas durante la suma pero tambin se desea res- total= p->info. + carry;
taurar posteriormente los apuntadores de la list a sus valores originales, s usan lis- number =total:% hunthou;
tas circulares con nodos cabecera. El nodo cabecera se distingue por su campo info insafter(s, number)
con valor~!. Por ejemplo, el entero 459763497210698463 se representa mediante la carry= total/ hunthou;
lista ilustrada en la figura 4.5.4. s = s->n.ext;
Ahora se escribir una funcin addint que acepta apuntadores a dos de esas lis' p = p->next;
I* fin de while * I
tas que representan enteros, crea una lista qu representa la suma de los enteros y da
while (q->info != -1)
como resultado un apuntador a: la lista sumiAmbas listas se recorrer\ en paralelo y
t_otal = .q->info + carry;
se suman cinco dgitos a la vez. Si la suma de dos nmeros de cinco dgitos es x, fos number =total% hunthou;
cinco dgitos de menor orden de x se pueden extraer mediante la expresin x O/o insafter(s~ nu~ber);
100000, que proporciona el residuo de x dividido entre 100000. El acarreo se puede carry~ total/ hu~thou;
calcular mediante la divisin de enteros x/100000. Cuando se alcanza el final de una s = s->next;
lista, dicho acarreo se propaga a los dgitos restantes de la otra lista. La funcin pro- q = q->next;
sigue y usa las rutinas getnode e insafter. I * fin deWhile * I
I* verificar si hay un acarreo adicional de los primeros *!
I* cinco dgitos *I
NODEPTR addint(p, q)
if (carry== 1)
NODEPTR p, q; insafter(s, carry);
{ s = s->next;
long int hunthou ~ 100000L; I* fin de if *t
long int carry, number, total;
I* s apunta al ltimo nodo _de la suma. s- >ne x t *I
NODEPTR s;
I* apunta: el nodo cabecera de la lista sum *!
/* Asignar a p y q los nodos siguientes a los nodos cabeceras
return(s->next);
P.~= p- >next;
I * fin de addint * I
q = q->next
/ * preparar un nodo cabecera pa_ra la suma *I
s - getnode();
s->info = -1 Listas doblemente ligadas
s->next = s;
I* al principio no hay acarreo */ Aunque una lista ligada de manera circular. tiene ventajas sobre una lista lineal,
carry= o; ,, an presenta algunos inconvenientes. No se puede recorrer tal lista en sentido inver-
while (p->info 1- -1 && q->info ! -1) so ni se puede borrar un nodo de una lista circular ligada, dando slo un apuntador
/ * sumar la informacin de los dos nodos * / al mismo. En los casos en que se requieran esas facilidades, la estructura de datos
!* y elacarreo previo *I
>apropiada es una lista doblemente ligada. Cada nodo de una lista de ese tipo con-
total = p->info + q->info +. carry;
*I tine dos apuntadores: uno a su antecesor y otro a su sucesor. En realidad, en el
I* Determinar los cinco dgitos de menor
!* orden de la suma e insertarlos en la lista . *I 'contexto de listas doblemente ligadas, los trminos antecesor y sucesor carecen de
number = total % hunthou; significado, ya que la lista es completamente simtrica. Las listas doblemente ligadas
insafter(s, number); < pueden ser lineales o circulares y pueden o no contener un nodo cabecera como se
* avance los Traversals *I ilustra en la figura 4.5.5.

230 Estructuras de datos en G,i


231
Se puede considerar que los nodos de na lista doblemente ligada consisten en Una operacin que puede ejecutarse sobre listas doblemente ligadas, pero no sobre
tres campos: un campo info que contiene la informacin almacenada en el nodo, y listas ligadas ordinarias, es la de eliminar un nodo determinado. La siguiente rutina
los campos /eft y right que contienen apuntadores a los nodos de cada lado. Es po- C elimina el nodo apuntado por p. de una lista doblemente ligada y almacena sus
sible declarar un conjunto de tales nodos, los que se valen de un arreglo o de una contenidos en x por medio de la implantacin dinmica. La rutina es llamada por
implantacin dinmica, mediante: de/ete(p, &x)

Implantacin de nodos Implantacin dinmica delete(p, px)


NODEPTR p;
struct nodetype struct node { int *px;
int info; int info; {
int left, right; struct node NODEPTR g, r
}; }; if (p NULL)
struct nodetype node[NUMNODESJ; typedef struct printf("eliminacin no efectuada\ n");
return;
Obsrvese que la lista disponible para tal conjunto de nodos en la implantacin I* fin dei *!
con arreglo no necesita estar doblemente ligada, ya que no se recorre de manera bidi, *px = p->in:f'o;
reccional. La lista disponible se puede ligar mediante el apuntador left o right. Por q p->left;
supuesto, hay que escribir las rutinas apropiadas getnode YJreenode: r p->right;
q->right r;
Ahora se presentan rutinas para operar sobre listas doblemente ligadas circula,
r~>left - q;
res. Una propiedad conveniente de dichas listas es que si pes un apuntador a algn freenode(p);
nodo, donde se deja a /eft(p) como abreviatura de node[p].!eft o a p - > left y return
right(p) como abreviatura de nodefp].right o p - > right, se tendr I * fin de delete *I
left(right(p)) p right(left(p))
, .. La rutina inse11right inserta un nodo con campo de informacin x a la derecha
~e node(p) en una hsta doblemente ligada:

insertright(p, x)
NODEPTR p;
int x;
{a) Una lista doblemente ligada {
NODEPTR q, r;
if (p NULL) {
printf("eliminacin no efectuada \n");
return;
I*. finde i *I
q - getnode();
q->info = x;
r = p->rLght;
r->left - q;
q->right - r;
q->left - p;
p->right - q;
return;
! * fin de insertrght */
{e) Una lista doblemente ligada circular con cabecera
?Al lector se le queda como ejercicio una rutina similar insert!eft para insertar un no-
Figura 4.5.5 Listas doblemente ligadas. ~R con campo de informacin x a la izquierda de node(p) en una lista doblemente
ligada.

232 Estructuras de datos en


233
Cuando la eficacia de espacio es importante, un programa no puede darse el Cuando se represent un entero positivo como una lista circular ligada, un campo
lujo de tener dos apuntadores para cada elemento de la lista. Existen varias tcnicas info de -1 indicaba un nodo cabecera. Con la nueva representacin, sin embargo,
para comprimir los apuntadores izquierdo y derecho de un nodo en un solo campo. un nodo cabecera puede contener un campo info, como 5, que es vlido para cual-
Por ejemplo, un campo de apuntador simple ptr para cada nodo puede contener la quier otro nodo de la lista.
suma de apuntadores de sus vecinos izquierdo y derecho. (Aqu, se presume que los. Hay varias maneras de remediar este problema. Una es agregar otro campo a
apuntadores se representan de un modo tal que es posible realizar aritmtica con cada nodo para indicar si es o no un nodo cabecera. Tal campo podra contener el
ellos de manera rpida. Por ejemplo, los apuntadores representados mediante valor lgico TRUE si el nodo es cabecera, y FALSE en caso contrario. Esto signifi-
ndices de un arreglo se pueden sumar y restar. Aunque en C no es legal sumar dos ca, por supuesto, que cada nodo requerira ms espacio. Una alternativa sera elimi-
apuntadores, muchos compiladores permitirn tal aritmtica de apuntadores. Dados nar el contador del nodo cabecera y un campo info -1 indicara un nmero positivo
dos apuntadores externos, p y q, a dos nodos adyacentes de manera que p = = y un campo info -2 uno negativo. Un nodo cabecera sera identificado entonces por
/eft(q), right(q) puede calcularse como ptr(q) - p y left(p) como ptr(p) - q. Da- su campo info negativo. Sin embargo, eso incrementara el tiempo necesario para
dosp y q, es posible eliminar cualquier nodo y reinicializar su apuntador al nodo an- . comparar dos nmeros, pues sera necesario contar el nmero de nodos de cada lis-
tecesor o sucesor. Tambin es posible insertar un nodo a la izquierda de node(p) o la ta. Tales relaciones de espacio/tiempo son comunes en computacin, por lo que se
derecha de node(q), o insertar un nodo entre node(p) y node(q) y reinicializar p o q tiene que tomar una decisin acerca de qu eficacia debe sacrificarse y cul debe
al nodo recin insertado. Mediante un esquema de este tipo, siempre es crucial man- retenerse.
tener dos apuntadores externos a dos nodos adyacentes de la lista. En nuestro caso, escogemos una tercera opcin, que es retener un apuntador
externo a la cabecera de la lista. Un apuntador p puede identificarse como apunta-
dor ala cabecera si es igual al apuntador externo original; en caso contrario node(p)
Suma de enteros largos mediante listas doblemente ligadas
no es un nodo cabecera.
La figura 4.5.6 indica un nodo muestra y la representacin de tres enteros co-
Para ilustrar el uso de las listas doblemente ligadas, considrese ampliar la
mo listas doblemente ligadas. Obsrvese que los dgitos menos significativos estn a
implantacin con lista de enteros largos para incluir enteros positivos y negativos. El
la derecha de la cabecera y que los contadores de los nodos cabecera no incluyen el
nodo cabecera de una lista circular que representa un enter largo contiene una indi-
propio nodo cabecera.
cacin que seala si el entero es positivo o negativo. Mediante la representacin anterior se presenta una funcin compabs que
Para. sumar un entero positivo y uno negativo, hay que restar el de menor vaor
compara el valor absoluto de dos enteros representados como listas doblemente liga-
absoluto del de mayor valor y darle al resultado el signo del ltimo. Por lo tanto, se
das. Sus dos parmetros son apuntadores a las cabeceras de la lista y su resultado es
necesita algn mtodo para averiguar cul de los dos enteros representados como lis-
1 si el primero tiene el mayor valor absoluto, -1 si lo tiene el segundo y Osi el valor
tas circulares tiene el mayor valor absoluto.
absoluto de dos enteros es igual.
El primer criterio que se puede usar p~ra identificar un entero con el mayor va-
lor absoluto es el de la longitud de los enteros (suponiendo que stos no contienen
compabs ( p, q)
ceros importantes). La lista con ms nodos representa el entero con valor absoluto
NODEPTB p, q;
mayor. Sin embargo, el conteo. real del nmero de nodos implica un recorrido extra
{
de la lista. En h.. a; :'e contar el nmero de nodos, el conteo podra guardarse como
0 NODEPTR r, s;
parte del nodo cabecera y referenciarse cuando fuese necesario. /* comparando el tamao de los enteros * /
Sin embargo, si las dos listas tienen el mismo nmero de nodos, el entero de
1

if (abs(p->info) > abs(q->info)) "


mayor valor absoluto ser aqul cuyo dgito ms significativo sea mayor. Si los pri- return(1);
meros dgitos son iguales en ambos enteros, habr que recorrer la lista del dgito ms i f (abs(p->info) < abs(q->info))
significativo al menos significativo para determinar qu nmero es mayor. Obsrve- ret.urn(-1);
se que este recorrido es en direccin opuesta al que se usa para sumar o restar dos en- /* ambos tienen el mismo tamao */
teros. Como se debe tener la capacidad de recorrer las listas en ambas direcciones, se r - p->left;
usan listas doblemente ligadas para representar enteros de este tipo. s = q->left;
Considrese el formato. del nodo cabecera. Adems de un apuntador izquierdo I* Recorrido de la lista a pai:tir del dgito ms significativo */
y uno derecho, la cabecera debe tener la longitud de la lista y una indicacin del sig- while (r !- p) {
if (_r->info > s->info)
no del nmero. Esas dos porciones de informacin pueden combinarse en un entero
return(1};
simple cuyo valor absoluto sea la longitud de la lista y cuyo signo sea el del nmero if (r->info < s->info)
que se est representando. Sin embargo, si se hiciera as, se destruira la posibilidad return(-1);
de identificar el nodo cabecera por medio del examen del signo de su campo info ..

Estructuras de datos en C, Colas y listas 235


234
la lista se usan variables auxiliares pptr y qptr. La suma se forma en una lista apun-
~ inlo I der. tada por la variable r .
(a) Un nodo muestra
NODEPTR addiff(p, q)
NODEPTR p, q;
{
int count;

(b) El entero -3242197849762


1? NODEPTR pptr, qptr, r, s, zeroptr;
long int hunthou = 1000001;
long int borrow, diff;
int zeroflag;
/ * inicializacin de variables * /
count = O
borrow = O;
zeroflag = FALSE;
* genera} un nodo cabecera para la suma *I
r = getnod();
r->left = r;
(e) El entero 676941 r->right = r;
; * Recorrido de las dos listas * /
pptr = p->right;
qptr = q->right;
while (qptr != q)
diff = pptr->info - borrow - qptr->info;
if (diff >= O)
borrow = o;
(dJ El encero O else {
diff = diff + hunthou;
Figura 4.5.6 Enteros con listas doblemente ligldas. borrow = 1;
!* fin de if *I
* generar un nuevo nodo e insertarlo */
r = r->left; /* a la izquierda del nodo cabecera de sum *I
s = s->left; insertleft(r, diff);
! * fin de while * I count += 1;
/* los valores absolutos son iguales */ / * prueba para el nodo cero * /
return(O); if (diff == O) {
! * fin de compabs *! if (zeroflag == FALSE)
zeroptr = r->left;
Ahora puede escribirse una funcin addijf que acepte dos apuntadores a listas zeroflag = TRUE
doblemente ligadas, las que representan enteros largos de signos diferentes, donde el
valor absoluto del primero no es menor que el del segundo, y que d como resultado else
un apuntador a una lista que representa la suma de los enteros. Por supuesto, hay zeroflag = FALSE;
que tener cuidado de eliminar los ceros no significativos de la suma. Para hacerlo, pptr = pptr->right;
qptr = qptr->right;
hay que mantener un apuntador zeroptr al primer nodo de un conjunto consecutivo
de ceros a la izquierda y un indicador zeroflag que es TRUE si y slo si el ltimo no-
/* fin de while * I
/* Recorrido del resto de la lista p
do de la suma generada hasta el momento es O. while (pptr != p) {
En esta funcin, p apunta al nmero con mayor valor absoluto y q al nmero diff = pptr->info - borrow;
con menor valor absoluto. Los valores de esas variables no cambian. Para recorrer if (diff >= O)
borrow = o;

236 Estructuras de datos en C Colas y listas 237


else {
diff = diff + hunthou;
borrow = :L; 4.5. t. Escriba un algoritmo y una rutina C para ejecutar cada una de las operaciones del
I* fin deif *! ejercicio 4.2.3 para listas circulares. Qu es ms eficaz de las listas circulares compa-
insertleft(r, diff); rado con las lineales? Qu es menos eficaz?
count += 1; 4.5.2. Escriba otra vez la rutina place de la seccin 4.3 para insertar un nuevo elemento de
if (diff O) una lista circular ordenada.
if ( zeroflag FALSE) 4.5.3. Escriba un programa para resolver el problema de Josephus usando un arreglo en lu-
zeroptr r->left; gar de una lista circular. Por qu es ms eficaz una lista circular?
zeroflag = TRUE;
4.5.4. Considere la siguiente variacin del problema de Josephus. Un grupo de personas es-
t en un crculo y cada una de ellas escoge un entero positivo. Se escoge un entero
else
positivo n y uno de los nombres. Se comienza con la persona cuyo nombre fue elegido,
zeroflag - FALSE;
se cuenta alrededor del crculo en el sentido de .las manecillas del reloj y se elimina la
pptr pptr->right;
persona n-sima. Para continuar el conteo, se usa el entero positivo elegido por la
I* fin de while *I
persona recin elim,.inada. Cada vez que se elimina una persona, se usa el nmero de
i f (zeroflag TRUE) I* eliminar ceros iniciales */
sta para eliminar a la siguiente. Por ejemplo, supngase que las cinco personas son
while (zeroptr ! r)
A, B, C, D y E, q\J.e stas eligieron los enteros 3, 4, 6, 2 y 7 respectivamente y que el
s = zeroptr;
primer nmero elegido es 2. Entonces, si se comienza con A, el orden con el que se eli-
zeroptr = zeroptr->right;
mina a las personas del crculo es B, A, E, C, dejando a D como el ltimo del crculo.
delete(s, &diff);
Escriba un programa que lea un grupo de lineas de entrada. Cada lnea de entrada,
count;-= :L;
excepto la primera y la ltima, contiene un nombre y un entero positivo escogido por
I* fin de f .. de while*I
esa persona. El orden de los nombres de los datos es el orden en el sentido de las ma-
I* insertar el tamao y el signo en et ndo cabecera *I
necillas de reloj de las personas del crculo, y el conteo debe comenzar con el primer
if (p->info > O) nombre de la entrada. La primera lnea de entrada contiene el nmero de personas del
r->info count; crculo. La ltima contiene slo un entero positivo simple que representa el conteo
else inicial. El programa imprime el orden en que se eliminan las personas del crculo.
r->info -count;
4.5.5. Escriba una funcin multint(p, q) en C para multiplicar dos enteros positivos largos
return(r);
representados individualmente por listas circulares ligadas.
I* fin de addiff *I
4.5.6. Escriba un prograni.a para imprimir el nmero lOO-simo de Fibonacci.
4.5. 7. Escriba un algoritmo y una rutina C para ejecutar cada una de las operaciones del
Tambin se puede escribir una funcin addsame para sumar dos nmeros de ejercicio 4.i.3 para listas doblell1ente ligadas. Cules son ms eficaces, las de las lis-
signo igual. Esta es muy similar a la funcin addintde la implantacin anterior, ex- tas doblemente ligadas o las de ligadura simple? Cules son menos eficaces?
cepto en que (rata con una lista doblemente ligada y en que tiene que mantener 4.5.8. Suponga que un campo de apuntador simple en cada nodo de una lista doblemente li-
actualizado el nmero de nodos de la suma. gada contiene la suma de los apuntadores a los nodos antecesor y sucesor, como se
Por medio de esas rutinas, se puede escribir una versin nueva de addint que describe en el texto. Dados los apuntadores p y q a dos nodos adyacentes de tal lista,
sume dos enteros representados por listas doblemente ligadas. escribir rutinas en C para insertar un nodo a la derecha de node(q), a la izquierda de
node(p) y entre node(p) y node(q), modificando p para que apunte al nodo recin in-
NODEPTR addint(p, q) sertado. Escriba una rutina adicional para borrar node(q), reinicializando q como el
NODEPTR p, q; sucesor del nodo.
{ 4.5.9. Suponga quefirst y /ast son apuntadores externos al primero y al ltimo nodo de una
I* verificar si los enteros Son del mismo signo */ lista doblemente ligada representada igual que en el ejercicio 4.5.8. Escriba rutinas en
if (p->info * q->info > O) C para implantar las opera~iones del ejercicio 4.2.3 para una lista de este tipo.
return(addsame(p, q));
4.5.10. Escriba una rutina addsame para sumar dos enteros largos del mismo signo represen-
I* determinar cul tiene el mayor valor absoluto */ tados por listas doblemente ligadas.
if (compabs(p, q) > O)
return(addiff(p, q)); 4.5. l t. Escriba una funcin multint(p, q) en C, para multiplicar dos enteros largos represen-
else tados por listas doblemente ligadas circulares.
return(addiff(q, p)); 4.5.12. Cmo se puede representar un polinomio de tres variables (x, y y z) por medio de
/* fin de addint * I una lista circular? Cada nodo debe representar un trmino y contener las potencias de

238 Estructuras de datos en C Colas y listas 239


x, y y z, as como el coeficiente de dicho trmino. Escribir funciones en C que hagan
lo siguiente:
a. Sume dos polinomios de ese tipo.
b. Multiplique dos polinomios de ese tipo.
c. Calcule la derivada parcial de un polinomio semejante con respecto a cualquiera
de sus variables.
d. Evale un polinomio semejante para valores dados de x, y y z.
e. Divida un polinomio por otro de ese tipo, creando un polinomio cociente y un
polinomio restante.
f. Integre un polinomio semejante con respecto a cualquiera de sus variables.
g. Imprima la representacin de semejante polinomio. Arboles
h. Dados cuatro polinomios de ese tipo: f(x, y, z), g(x, y, z), h(x, y, z) e i(x, y, z),
calcule el polinomio f(g(x, y, z), h(X, y, z) i(x, y, z)).

En este captulo se analizar una estructura de datos que es til en muchas aplica-
ciones: el rbol. Se definen varias formas de esta estructura de datos y se muestra
~mo pueden representarse en C y cmo se pueden aplicar para resolver una amplia
gama de problemas. Al igual que con las listas, los rboles se tratan principalmente
como estructuras de datos y no como un tipo de datos. Es decir, el inters inicial es
por la impl,antacin ms que por la definicin matemtica.

Un rbol binario es un conjunto finito de elementos que o est vaco o est dividido
en tres subconjuntos desarticulados. El primer subconjunto contiene un solo ele-
mento llamado raz del rbol. Los otros dos son en s mismos rboles binarios, lla-
mados subrboles izquierdo y derecho del rbol original. Un subrbol izquierdo o
derecho puede estar vaco. Cada elemento de un rbol binario se llama nodo del
rbol.
En la figura 5.1. l se muestra un mtodo convencional de dibujar un rbol bi-
nario. Este rbol consi&te de nueve nodos y tiene a A como raz. Su subrbol
izquierdo tiene a B como raz y su subrbol derecho a C. Esto queda sealado por las
dos ramas que salen de A.: hacia B a la izquierda y hacia Ca la derecha. La ausencia
de ramas indica que es un subrbol vaco. Por ejemplo, tanto el subrbol izquierdo
del rbol binario que tiene raz en C como el subrbol derecho del rbol binario
que tiene raz en E estn vacos. Los rboles binarios con raz en D, O, He I tienen
subrboles derecho e izquierdo vacos.

240 Estructuras de datos en G 241


A

Figura 5.1.1 Un rbol binario.

La figura 5.1.2 ilustra algunas estructuras que no son rboles binarios. Hay
que asegurarse de haber entendido por qu no lo son.
Si A es la raz de un rbol binario y Bes la raz de su subrbol derecho o iz-
quierdo entonces A se llama el padre de By Bel hijo izquierdo o derecho de A. Un
nodo que no tiene hijos (como D, G, H o I de la figura 5.1.l) se llama hoja. El nodo Figura 5.1.2 (corit.).

A n.1 es un ancestro del nodo n2 (y n2 es un descendiente den 1) sin I es el padre de n2


o el de algn ancestro de n2. Por ejemplo, en el rbol de la figura 5. l. l, A es unan-
cestro de G y Hes un descendiente de C, pero E no es ni ancestro ni descendiente de
C. Un nodo n2 es un descendiente izquierdo de un nodo n I si n2 es el hijo izquierdo
den I o un descendiente del hijo izquierdo den 1. Un descendiente derecho puede de-
finirse de manera similar. Dos nodos son hermanos Si son hijos derecho e izquierdo
del mismo padre.
Aunque los rboles naturales crecen con sus races en la tierra y sus. hojas en el
fiire, los cientficos de la computacin represntan, casi a nivel" universal, las estruc-
turas de datos como rboles con la raz en la cspide y las hojas en el fondo. La r\'
direccin de la raz hacia las hojas se llama "hacia abajo", y de las hojas a la raz '.,
;.'
"hacia arriba". Ir de las hojas a la raz se llaina "trepar" a un rbol; e fr de la raz a '

(a) las hojas, "descender" de un rbol.

. (b)
Fiflra 5.1.2 Estructllras que no
son rboles binarios.
e) Figura 5.1. 3 Un rbol estrictamente binario.

242 Estructuras de datos en C 243


Si un nodo que no es una hoja de un rbol binario tiene subrboles izquierdo des igual a l menos que el nmero de veces que hay que multiplicar 2 por s mismo
derecho no-vacos: el rbol se llama rbol estrictamente binario. As, el rbol de 1! para alcanzar tn + l. En matemticas, /og,;c se define como el nmero de veces que
figura 5.1.3 es e:tnctamente binario, mientras que el de la figura 5.1.1, no 0 es (por- hay que multiplicar b por s mismo para alcanzar x. Por lo tanto, se puede decir que,
que los nodo.s C y.E tienen, cada uno, un solo hijo). Un rbol estrictamente binario en un rbol binario completo, des igual a log 2(1n + l) - l. Por ejemplo, el rbol
con n hoJas. t'.ene siempre 2n - I nodos. Al lector se le deja la comprobacin de esto binario completo de la figura 5.1.4 contiene 15 nodos y es de profundidad 3. Advirta-
corno eJerc1c10. se que 15 es igual a 23 + 1 l y 3 es igual a log 2(15 + l) -l. log 2x es mucho ms
. El nivel de u? nodo en un rbol binario se define como sigue. La raz del rbol .pequeo que x [por ejemplo, log 2 1024 es igual a 10 y log 2 1000000 es menor que 20].
tiene mvel O Y el mvel de cualquier otro nodo del rbol es uno ms que el nivel d La importancia de un rbol binario completo es que es el rbol binario con el nmero
d p . 1 1. esu
pa re. or eJemp o, en e arbol binario de la figura 5. l. l, el nodo E est en el nivel 2 de nodos mximo para una profundidad determinada. Dicho de otro modo, aunque
Y el H en el 3. La profundidad de un rbol binario es el mximo nivel de cualqu un rbol binario completo contenga muchos nodos, la distancia de la raz a cual-
hoja ddrboL Esto es igu~I a la longitud del camino ms largo de la raz a cu~~~ quier hoja (la profundidad del rbol) es relativamente pequea.
qmer hoJa.. As1, la p~ofund1dad ~el rbol de la figura 5.1.l es 3. Un rbol binario Un rbol binario de profundidad des un rbol binario cuasi-completo si:
completo de .profundidad des el arbol estrictamente binario cuyas hojas estn en el
mvel d: La _figura .5.L4 ilustra el rbol binario completo de profundidad 3. t. Cada hoja del rbol est en el nivel do en el nivel d - l.
S1 un arbol bmano contiene m nodos en el nivel 1, contiene a lo sumo 2m nodos 2. Para todo nodo nd del rbol con un descendiente derecho en el nivel d, todos
e~ el mvel 1.+ l. Como ~n rbol binario puede contener a lo sumo un nodo en el los descendientes izquierdos de nd que sean hojas tambin estarn en el nivel d.
mvel O(la raiz), contendra ~ lo su1;10 2 1 nodos en el nivel/. Un rbol binario comple-
to de profundidad des el arbol bmano de profundidad d que contiene la cantidad El rbol estrictamente binario de la figura 5.J.5a no es cuasi-completo, pues con-
1
e:rncta 2 nodos.en cada nivel I entre Oy d. (Esto equivale a decir que es el rbol bina- tiene hojas en los niveles l, 2 y 3, con lo que viola la condicin l. El rbol estricta-
n? de profundidad d que contiene la cantidad exacta 2d nodos en el nivel d). El mente binario de la figura 5. l.5b satisface la condicin l, ya que todas las hojas estn
numero total ~e nodos de un rbol binario completo de profundidad d, tn, es igual a en el nivel 2 o en el 3. Sin embargo, la condicin 2 no se satisface, pues A tiene un
la suma del numero de nodos de cada nivel entre O y d. As, descendiente derecho en el nivel 3 (]), pero tambin tiene un descendiente izquierdo
en el nivel 2 (E), el que es una hoja. El rbol estrictamente binario de la figura 5.1.Sc
d
In = 2 + 2 1 + 22 + + 2" = i satisface ambas condiciones, la 1 y la 2, y es, en consecuencia, un rbol binario
cuasi-completo. Aunque el rbol binario de la figura 5. l.5d es tambin un rbol
j=O
binario cuasi-completo, no es estrictamente binario, pues el nodo E tiene un hijo
Po: inducci~n, se puede mostrar que esta suma es igual a 2d + 1 _ 1. Como todas las izquierdo pero no uno derecho. (Hay que observar que muchos textos se refieren a
hoJas d~ un arbol de este tipo estn en el nivel d, el rbol contiene 2dhojas y, en con- tales rboles como "rboles binarios completos" y no corno "rboles binarios cuasi-
secuencia, 2" - l nodos que no son hojas. completos". Otros textos usan el trmino "completo" o "completamente binario"
De manera anloga, si se sabe el nmero de nodos, tn, de un rbol binario com- para referirse al concepto que aqu se usa como "estrictamente binario". Aqu se
pleto, se puede calcular su profundidad, d, por medio de la ecuacin In = 2d + 1 _ l. aplican los trminos "estrictamente binario", "completo" y "cuasi-completo" co-
mo se definen en el texto.)
Los nodos de un rbol binario cuasi-completo pueden enumerarse de manera
que se le asigne 1 a la raz, al hijo izquierdo se le asigna el doble del nmero asignado
a su padre y al derecho uno ms que el doble del nmero asignado a su padre. Las
figuras 5. l .5c y d ilustran esta tcnica de enumeracin. A cada nodo de un rbol bi-
nario cuasi-completo se le asigna un nmero nico que define su posicin dentro del
rbol.
Un rbol estrictamente binario cuasi-completo con n hojas tiene 2n
nodos, como ocurre con cualquier otro rbol estrictamente binario con n hojas. Un
rbol binario cuasi-completo con n hojas que no sea estrictamente binario tiene 2n
nodos. Hay dos rboles binarios cuasi-completos distintos con n hojas, uno de los
cuales es estrictamente binario y el otro no. Por ejemplo, los rboles de las figuras
5. l.5c y d son cuasi-completos y tienen 5 hojas; sin embargo, el rbol de la figura
5. l.5c es estrictamente binario y el de la figura 5. l.5d no.
Figura 5.1.4 Uh rbol binario completo de nivel 3.

244 Arboles 245


Estructuras de datos en e

-- - ---~----~-
Hay slo un rbol binario cuasi-completo con n nodos. Ese rbol es estricta-
mente binario si y slo si n es impar. Por lo tanto, el rbol de la figura 5. l .5c es el
nico rbol binario cuasi-completo con nueve nodos y es estrictamente binario por-
B . que9 es impar, mientras que el de la figura 5. l.5d es el nico rbol binario cuasi-
completo con 10 nodos y no es estrictamente binario pues I Oes par.
Un rbol binario cuasi-completo de profundidad des intermedio entre el rbol
binario completo de profundidad d 1, que contiene 2d - I nodos, y el rbol bina-
rio completo de profundidad d, que contiene zd+ 1 - I nodos. Si tn es el .nmero
total de nodos de un rbol binario cuasi-completo, su profundidad es el entero ms
grande que sea menor o igual que log 2tn. Por ejemplo, los rboles binarios cuasi-com-
pletos con 4, 5, 6 y 7 nodos tienen profundidad 2, y los rboles binarios cuasi-
completos con 8, 9, 10, I I, 12, 13, 14 y 15 nodos tienen profundidad 3.

Operaciones con rboles binarios


(a)
(b)

Existen varias operaciones primitivas que pueden aplicarse a rboles binarios.


Si pes un apuntador a un nodo nd de un rbol binario, la funcin info(p) da como
resultado los contenidos de nd. Las funciones /eft(p), right(p), father(p) y
brother(p) dan como resultado apuntadores al hijo izquierdo, hijo derecho, padre y
herman de nd, respectivamente. Esas funciones dan como roesultado el apuntador
nulo.si nd no tiene hijo izquierdo, hijo derecho padre o hermano. Como consecuen-
cia, las funciones lgicas isleft(p) e isright(p) dan como resultado verdadero (true) si
4 nd es hijo izquierdo o derecho, respectivamente, de algn otro nodo del rbol, y fal-
7
(false) en caso contrario.
Obsrvese que las funciones isleft(p), isright(p) y brother(p) pueden implan-
tarse mediante las funciones left(p), right(p) y father(p). Por ejemplo, is/e/tpuede
8 implantarse como sigue:

q = father(p);
(1:)
i f (q == null)
return(false); f* p apunta a la raz *!
i f (left(q) == p)
return (true);
return( false);
' B 3

o, de manera ms simple, como father(p)&& = = left(father(p)).isright puede


implantarse de manera similar, o llamando a isleft. brother(p) puede implantarse
mediante isleft o isrigh1 como sigue:

i f (father(p) == nu.ll)
return(n ull); I* p apunta a la raz *!
i f ( isleft(p))
return(right(father(p)));
(d) return( left( father(p)));

Figura 5.1.5 Enumer<1cin de nodos para rboles binarios cuasicompletos. Las operaciones maketree, set/eft y selright son tiles en la construccin de un
rbol binario. makelree(x) crea un nuevo rbol binario que consiste en un solo nodo

246 Estructuras de datos en C


247
con campo de informacin x y da corno resultado un apuntador a dicho nodo. i
setleft(p,x) acepta un apuntador p a un nodo de un rbol binario sin hijo izquierdo:c;
Esto crea un nuevo hijo izquierdo de node(p) con campo de informacin x:
setrigth(p,x) es anloga a setleft, excepto en que crea un hijo derecho de node(p);

Aplicaciones de rboles binarios


. ,-,/;
Un rbol binario es una estructura de datos til cuando en cada punto de un
proceso hay que tornar una decisin de doble opcin. Por ejemplo, supngase que se
desea encontrar todos los duplicados en una lista de nmeros. Una manera de ha,
cerio es comparar cada nmero con todos los que lo preceden. Sin embargo, estb;
implica un enorme nmero de comparaciones.
El nmero de comparaciones puede reducirse mediante un rbol binario. El 5
primer nmero de la lista se coloca en un nodo que se establece corno la raz de un.
rbol binario con subrboles izquierdo y derecho vacos. Cada nmero sucesivo de Figura 5.1.6, Un rbol binnrio construido para encontrar dup!icdos.
la lista se compara despus con el nmero que est en la raz. Si hay coincidencia, se
obtiene un duplicado. Si es ms pequeo, se exrnina el subrbol izquierdo; si es rn-'
yor, se examina el subrbol derecho. Si el subrbol est vaco, el nmero no es un Otra operacin comn es recorrer un rbol binario; esto es, pasar a travs del
duplicado y se coloca en un nodo nuevo en esa posicin del rbol. Si el subrbol n . rbol, ~numerando cada uno de sus nodos una vez. Quiz slo se desee imprimir los
est lleno, el nmero se compara con los contenidos de la raz. del subrbol y sei contemdos de cada nodo al enumerarlos, o procesar los nodos en otra forma. En
repite todo el proceso con el subrbol. A continuacin se presenta un algoritmo para;~ cualqmer caso, se habla de visitar cada nodo al enumerar ste.
El orden en que se visitan los nodos de una lista lineal es, de manera clara del
hacer esto.
primero al ltimo. Sin embargo, no hay tal orden lineal "natural" para los nod;s de
*I
un rbol. Asi, se usan diferentes ordenamientos para el recorrido en diferentes ca-
!* leer el primer nmero e insertarlo en un
rbol binario de un slo nOdo *I sos. Enseguida se definen tres de estos mtodos de recorrido. En cada uno de ellos,
I*
scanf( 11 %du, &number); no hay que hacer nada para recorrer un rbol binario vaco. Todos los mtodos se
tree = maketree(number); defin~n en for11;a recursiva,. de manera que el recorrido de un rbol binario implica
while (hay nmeros rezagados en la entrada) la visita de la ra1z y el recomdo de sus subrboles izquierdo y derecho. La nica dife-
scanf(H%d", &number); rend.a entre los mtodos es el orden en que se ejecutan esas tres operaciones.
p = q = tree; Para recorrer un.rbol binario lleno en preorden (conocido tambin corno or-
~hile (number != info(p) && q != NULL) { den con prioridad a la profundidad o depth-first arder), se ejecutan las siguientes
p = q; tres operaciones: .
i f (number < info(p))
q left(p); l. Visitar la raz.
else 2. Recorrer el subrbol izquierdo en preorden.
q right(p);
/* fin de while * I
3. Recorrer el subrbol derecho en preorden.
il (number= =ino(p)}
printf(' 1%d %s\n 11 , number, ''esunduplicado");
Para recorrer un rbol binario lleno en orden (u orden simtrico):
I * insertar number a la derecha o izquierda de p *I l. Recorrer el subrbol izquierdo en orden.
else i f (number < info(p)) 2. Visitar la raz.
setleft(p, number);
3. Recorrer el subrbol derecho en orden.
else
setright(p, number);
Para recorrer un rbol binario lleno en pos/orden:
I * fin de while *I
l. Recorrer el subrbol izquierdo en postorden.
La figura 5. 1.6 ilustra el rbol construido a partir de los datos de entrada !4, 15,
2. Recorrer el subrbol derecho en postorden.
9, 7, 18, 3, 5, 16, 4, 20, 17, 9, 14, 5.

Estructuras de datosen
Arboles 249
248
3. Visitar la raz. Un rbol binario de este tipo tiene la propiedad de que todos los elementos del
subrbol izquierdo de un nodo n son menores que los contenidos den y todos los ele-
A mentos del subrbol derecho n son mayores o iguales que los contenidos den. Un r-
.:'bol binario que tenga esta propiedad se llama rbol de bsqueda binaria. Si un rbol
de bsqueda binaria se recorre en orden (izquierdo, raz, derecho) y se imprimen los
Contenidos de cada nodo cuando se visita el mismo, los nmeros se imprimen en
: orden ascendente. Hay que convencerse de que ste es el caso para el rbol de
bsqueda binaria de la figura 5.1.8. Los rboles de bsqueda binarios y su uso en or-
denamiento y bsquedas se analizan en las secciones 6.3 y 7 .2.
Como otra aplicacin de los rboles binarios, considrese el siguiente mtodo
representacin de una expresin que contiene operandos y operadores binarios
por medio de un rbol estrictamente binario. La raz del rbol estrictamente binario
" contiene un operador que se debe aplicar a los resultados de evaluar las expresiones
Preorden: ABDGCEHIF representadas por los subrboles izquierdo y derecho. Un nodo que represente un
En orden: DGBAHEICF
Postorden: GDBlilEFCA operador no es una hoja, mientras que uno que represente un operando es una hoja.
La figura 5.1.9 ilustra algunas expresiones y sus representaciones con rboles. (El
carcter "$" se usa una vez ms para denotar la exponenciacin.)
Ahora se ver qu ocurre cuando estos rboles de expresiones binarios se re-
corren. Recorrer un rbol en preorden significa que el operador (la raz) precede a
sus dos operandos (los subrboles). As, un recorrido en preorden conduce a la
expresin en forma prefija. (Para las definiciones de las formas prefija y postfija de
e

. Preorden: ABCE/FJDGHKL
En orden: EICFJBGDKHLA Figura 5.1.7 Arboles binarios y
Postorden: IEJFCGKLHDBA sus recorridos,

La figura 5.1.7 ilustra dos rboles binarios y sus recorridos en preorden, orden y
post orden.
Muchos algoritmos que usan rb_oles binarios proceden en dos fases. La prime-
ra construye un rbol binario y la segunda lo recorre. Como ejempl0 de un algorit-
mo de este tipo, considrese el siguiente mtodo de ordenamiento. Dada una lista de
nmeros en un archivo de entrada, se desea imprimirlos en orden ascendente. Luego.
de leer los nmeros, se pueden insertar en un rbol binario como el de la figura
5.1.6. Sin embargo, a diferencia del algoritmo usado para encontrar duplicados, en
este caso los valores repetidos se colocan en el rbol. Al comparar un nmero con los
contenidos de un nodo del rbol, se toma una rama izquierda si el nmero es menor
que los contenidos del nodo y una rama derecha si es mayor o igual que los conteni-
dos del nodo. As, la lista de entrada es

14 15 4 9 7 18 3 5 16 4 20 17 9 14 5
Figura 5.1.8 Un rbol binario construido para un ordenamiento.
se produce el rbol binario de la figura 5.1.8.

250 Estructuras de datos en C 251


una expresin artimtica, ver las secciones 2.3 y 3.3). Recorrer los rboles binarios
de la figura 5.1.9 da como resultado las formas prefijas
e
+A* BC (Figura 5. l. 9a)

* + ABC (Figura 5. l.9b)


+A* -BC$DEF (Figura 5. l. 9c)
$ +A * BC * + ABC (Figura 5. l. 9d)
De manera similar, recorrer el rbol binario de una expresin en postorden co-
(a)A +BC (b)(A +B)C
loca un operador despus de sus dos operandos, de tal suerte que un recorrido en
postorden produce la forma postfija de la expresin. Los recorridos en postorden de
los rboles binarios de la figura 5.1.9 dan como resultado las formas postfijas
AB C * + (Figura 5. l. 9a)
AB + C (Figura 5. l. 9b)

ABC - DE F * * +
$ (Figura 5. l. 9c)
$
ABC * + AB +C * $ (Figura 5. l. 9d)

Qu ocurre si el rbol binario de una expresin se recorre en orden? Ya que la


raz (el operador) se visita despus de los nodos del subrbol izquierdo y antes de los
del derecho (los dos operandos), puede esperarse que un recorrido en orden conduz-
ca a la forma infija de la expresin. De hecho, cuando se recorre el rbol binario de
la figura 5.1.'ia, se obtiene la expresin infija A + B * C. Sin embargo, el rbol
.: de expresin binaria no contierie parntesis, pues el orden de las operaciones se
implica por la estructura del rbol. As, una expresin cuya forma in fija requiere pa-
rntesis para modificar las reglas de prioriJad convencionales no se puede recuperar
(e) A + (B - C) * D$(t' F) mediante un simple recorrido en orden. Los recorridos en orden de los rboles de la
figura 5.1.9 conducen a las expresiones

+A* BC (Figura 5. l.9a)


A+ B * C (Figura 5. l.9b)
A+B-CDE,,,F (Figura 5. l.9c)
A+BCA+BC (Figura 5. l. 9d)

que son correctas, excepto por los parntesis.


id

(d) (A +B C) S ((A + B) C)
5. 1.1. Pruebe que la raz de un rbol binario es un ancestro de todo nodo del rbol, excepto
Figura 5.1.9 Expresiones y sus representaciones mediante rboles bnarios. de ella misma.
5. t.2. Pruebe que un nodo de un rbol binario tiene a lo sumo un padre.

252 Estructuras de datos en C Arboles 253


S. 1.3. Cuntos ancestros tiene un nodo de un rbol binario que est en el nivel n? Representacin con nodos de rboles binarios
Compruebe su respuesta.
5.1.4. Escriba algoritmos recursivos y no recursivos para determinar: Al igual que en el caso de los nodos de una lista, los nodos de un rbol pueden
a. el nmero de nodos de un rbol binario implantarse corno un arreglo de elementos o corno asignacin de una variable din-
b. la suma de los contenidos de todos los nolos de un rbol binario mica. Cada nodo contiene los campos info, !ejt, right y jather. Los campos left,
c. la profundidad de un rbol binario right y jather de un nodo apuntan a los nodos hijo izquierdo, hijo derecho y padre,
S. I.5. Escriba un algoritmo para determinar si un rbol binario es respectivamente. Por medio de la implantacin con arreglo, se puede declarar:
a. estrictamente binario
b. completo #define NUMNODES 500
c. cuasiffcompleto struct nodetype {
.s. 1.6. Pruebe que un rbol estrictamente binario con n hojas contiene 2n - 1 nodos. int .info;
S. 1. 7. Dado un rbol estrictamente binario Con n hojas, sea !eve/(i) para i entre 1 y n igual al int left;
nivel de la i~sima hoja. Pruebe que int tight;
int father;
,1
"
21e1,.,/(1)
};
struct nodetype node(numnodesJ;
i=

Con esta representacin, las operaciones info(p), /ejt(p), right(p) y father(p)


S. 1.8. Pruebe que los nodos de un rbol estrictamente binario cuasi-completo con n hojas
estn implantadas mediante referencias a node[p].info, node[p].left, node[p].right
pueden enumerarse desde 1 hasta 2n -1 de manera tal que el nmero asignado al
ynode[p].jather, respectivamente. Las operaciones is!eft(p) isright(p) y brother(p)
hijo izquierdo del nodo nuncrado con i sea 2i y el asignado al hijo derecho numera-
do con i sea 2i + l. pueden implantarse en trminos de las operaciones left(p), right(p) y jather(p), co-
S. 1.9. Dos rboles binarios son sb'nilares s_i ambos estn va_cos, o no estn vacos, sus subr-
mo se describe en la seccin precedente:
boles izquierdos son similares y los derechos tani.bin. Escriba. un algoritmo para Para implantar isleft e isright de manera ms eficaz, tambin se puede incluir
determinar si dos rboles binarios son similares. dentro de cada nodo un indicador adicional isleft. El valor de este indicador es
5.1.10. Dos rb_oles binarios son similarmente especulares si ambos estn vacos o no vacos, TRU.E si el nodo es un hijo izquierdo y FALSE si no lo es. La raz est identificada
y el subrbol izquierdo de cada uno es reflejo del subrbol.derecho del otro. Escriba. slo por un valor NULL (0) en su campojather: Es. comn que el apuntador externo
un algoritmo para determinar si dos rboles binarios son similarmente especularesi al rbol apunte a su raz.
5.1.1 l. Escriba algoritmos para determinar si un rbol binario es o no similar y similarmente Otra posibilidad es que el signo del campo father sea negativo si el nodo es un
espectlar (ver ,el ejerc.icio previo) a algn subrbol de otro. hijo izquierdo o positivo si es un hijo derecho. El apuntador al padre de un nodo es-
5.1.12. Desarrolle un al'goritmo para encontrar duplicados en una lista de nmeros sin usar t dado entonces por el valor absoluto del campo jather. Las operaciones is!ejt o
un rbol binario. Si en la lista hay n nmeros distintos, cuntas veces se deben com~ isright necesitarn entonces examinar solamente el signo del campo father.
parar ds nmeros por uniformidad con su algoritmo? Qu ocurre si todos los n n~ Para implantar brother(p) de manera ms eficaz, se puede incluir tambin un
meros son iguales? campo brother adicional en cada nodo.
S. 1.13. a. Escriba un algoritmo que acepte un apuntador a un rbol de bsqueda binaria Una vez que se declara el arreglo de nodos, es posible crear una lista disponible
y elimine el elemento ms pequeo del rbol. ejecutando las siguientes instrucciones:
b. Muestre cmo implantar una cola de priori.dad ascendente (ver seccin 4.1) como
un rbol de bsqueda binaria. Presente algoritmos para las operaciones pqinsert int avail, i;
y pqmindelete en un rbol de bsqueda binaria.
S. 1.14. Escriba un algoritmo que acepte un rbol binario con el que se representa una expre-
sin y que d como resultado la versin i'nfija de la expresin que contenga slo avail = 1;
a9ueHos parntesis necesarios. for ( 1~0; i < NUMNODES; i++)
node[iJ.left ~ i + 1;
node[NUMNODES-1].left ~ O;
5.2. REPRESENTACIONES DE ARBOLES BINARIOS

En esta seccin se examinarn varios mtodos de implantacin de rboles binarios


en C y se presentarn rutinas que los construyen y recorren. Tambin se presentan Las funciones getnode y freenode son directas y se dejan corno ejercicio. Advirtase
algunas aplicaciones adicionales de rboles binarios. que la lista disponible no es un rbol binario sino una lista lineal cuyos nodos estn

254 Estructuras de datos en C 255


ligados por medio del campo left. Cada nodo de un rbol se toma, cada vez que se if (p == NULL)
requiere, del recipiente que contiene los disponibles y se devuelve a dicho recipiente printf("insercin no efectuada \ n");
cuando deja de usarse. Esta representacin se llama representacin con arreglo liga- else if (p->left != NULL)
printf(' 1insercn invlida \n'')
da de un rbol binario.
De manera alterna, un nodo puede definirse por else
p->left = maketree(x);
I * fin de setleft */
struct nodetype {
int info;
struct nodetype * izquierdo . La rutina setright (p,x) para crear un hijo derecho de node(p) con contenidos x
struct nodetype *derecho es s1m1lar y se deja como ejercicio al lector.
struct nodetyp *father; No. siempre es_ necesario usar los campos father, /eft y right. Si un rbol se
}; recorre siempre hacia abajo (de la raiz a las hojas), la operacin father no se usa
typedef struct nodetype NODEPTR; rnunca; en ese caso, un campo father no es necesario. Por ejemplo, los recorridos en
"Preorden, en ord~n y postor_den ~o usan el campo father. De manera anloga, si un
Las operaciones info(p), left(p) right(p) y father(p) se implantaran mediante refe- .arbol se recorre siempre hacia arnba (de las hojas a la raz), no se necesitan los cam-
rencias a p - > info, p - > left, p - > right y p - > father, respectivamente. pos!eft Y nght. Las operaciones isleft e isright pueden implantarse incluso sin usar
Con esta implantacin, no se necesita una lista disponible explicita. Las rutinas get- /ls campo_s_ left Y right si s~ usa un apuntador con signo en el campo father bajo la
node y freenode asignan y liberan .nodos llamando a las rutinas mal/oc y free. Esta 1mpla?tac10n con arreglo hg~da, como se analiz con anterioridad: un hijo derecho
representacin se llama representacin con nodos dinmica, de un rbol binario. se md1ca con un valor pos1t1vo de father y uno izquierdo con uno negativo. Desde
La representacin con rreglo ligada y la representacin con nodos dinmica /luego que e_ntonces hay que modificar las rutinas maketree, set/eft y setright de ma-
son implantaciones de una representacin ligada abstra.cta (llamada tambin repre- ,n.ern apropiada para esas representaciones. Bajo la representacin con nodos din-
sentacin con nodos) en la que los apuntadores explicitos ligan los nodos de un rbol mica,. ~e reqme~e un ~ampo lgico is/eft adems de father si se desea implantar las
binario. 9perac10~es _,snght e 1s/eft y s1 los campos left y right no estn presentes.
Ahora se presentarn implantaciones en C de las operaciones con rboles bina- .El ~1gmente progra~a usa un rbol de bsqueda binaria para encontrar nme-
rios bajo la representacin con nodos dintnica y al lector se le deja como ejercida ':'fQS duplicados en un archivo de entrada en el que cada nmero est-en una lnea de
las implantaciones con arreglo ligadas. La funcin maketree, que asigna un nodo y ;'eWada ?r separado. El programa es casi igual que el algoritmo de la seccin 5.1.
lo inicializa como la raz de un rbol binario de un solo nodo, puede escribirse como <Como .solo se usan ligas hacia abajo, no se necesita el campo father.
sigue:
struct nodetype {
NODEPTR maketree(x) " int info;
int x; struct nodety.pe *left;
{ struct nodetype *right;
NODEPTR p; };

p = getnode(); typedef struct nodetype NODEPTR;


p->info = x;
p->left = NULL; main()
p->right = NULL; {
return(p); NODEPTR ptree;
I * fin de maketree */ NODEPTR p, q;
:i
int number;
La rutina setleft(p,x) inicializa un nodo con contenidos x como el hijo izquierdo
node(p): scanf( 11 %'d", &n1,1mber);
ptre~ = maketree(n~mber);
setleft(p, x) while (scanf( 11 %d 11 , &number) != EOF)
NODEPTR p; p = q = ptree;
int x; while (number != p->info && q != NULL)
p = q;

256 Estructuras de datos en, 257


if (number < p->info) hijo izquierdo es dos veces el nmero asignado a su padre y el del hijo derecho es dos
q p- >izquierdo veces ms I el nmero asignado a su padre. Se puede representar un rbol binario
else cuasi-completo sin ligas/a/her, left o right. En lugar de ello, los nodos pueden guar-
q p - > dei_:echo darse en un arreglo info de tamao n. Aqu se alude al nodo de la posicin p slo
I * fin de while * ! como "nodo p". info[p] guarda los contenidos de nodo p.
if (number == p->info) En C, los arreglos comienzan en la posicin O, por lo tanto, en lugar de enume-
printf("o/od est duplicado\ n 11 1 nmero);
rar los nodos del rbol desde 1 hasta n, se enumeran desde O hasta n - l. A causa
else if (number < p->info}
setleft(p, number); del corrimiento en una posicin, Is dos hijos de un nodo enumerado con p estarn
else en las posiciones 2p + 1 y 2p + 2, en lugar de 2p y 2p + l.
setright(p, number); La raiz del rbol est en la posicin O, de tal manera que tree, el apuntador
I * fin de while */ externo a la raiz del rbol, es siempre igual a O. El nodo que est en la posicinp (o
}, / * fin de man !* sea, nodo p) es el padre implcito de los nodos 2p + 1 y 2p + 2. El hijo izquierdo de
node p es nodo 2p + 1 y el derecho, nodo 2p + 2. As, la operacin /eft(p)
se implanta por medio de 2 * p + 1 y right(p) por medio de 2 .* p + 2. Dado un hijo
Nodos internos y externos izquierdo en la posicin p, su hermano derecho est en la p + 1, y dado un hijo de-
recho en la posicin p, su hermano izquierdo est en la p - l.father(p) se implanta
Por definicin, los nodos hoja no tienen hijos. As, en la representacin ligada . mediante (p 1)/2. p apunta a un hijo izquierdo si y slo si pes impar. As, verifi-
de rboles binarios slo se necesitan los apuntadores left y right para nodos que no car si nodo p es un hijo izquierdo (la operacin isleft) equivale a verificar que p%2
sean hojas. A veces se usan dos conjuntos separados para nodos hojas y nodos que . no es igual a O. La figura 5.2.1 muestra arreglos que representan.los rboles binarios
no lo sean. Los nodos que no son hojas contienen campos info, left y right (con free cuasi-completos de las figuras 5.1.Sc y d.
cuenda no hay informacin asociada a los nodos que no son hojas, de manera que el Es posible extender esta representacin con arreglo implcito de rboles bina-
campo info es innecesario) y se asignan como registros dinmicos o como un arreglo rios cuasi-completos a rboles binarios en general. Para ello, hay que identificar un
de registros que se maneja usando una lista disponible. Los nodos hoja no contienen arbol binario cuasi-completo que contenga a! rbol binario qne se representa, La
campos feftni right y se guardan como un arreglo info simple que s asigna en orden figura 5.2.2a ilustra dos rboles binarios que no son cuasi-completos y la 5.2.2b
secuencial cuando se necesita (se asume que las hojas no se liberan nunca, lo que el rbol binario cuasi-completo ms pequeo que contiene a cada nno de ellos. Por
ocurre con frecuencia). Como alternativa, esos nodos se pueden asignar como va- ltimo, la figura 5.5.2c ilustra las representaciones con arreglo implcitas de estos r-
riables dinmicas que contengan slo un valor info. Esto ahorra una gran cantidad boles binarios cuasi-completos y, por extensin, de los rboles binarios originales. La
de espacio, pues las hojas representan con frecuencia la mayora de los nodos en un representacin con arreglo implcito tambin se llama representacin secuencial, en
rbol binario. Cada nodo (hoja o no) puede contener tambin un campofather si es contraste con la representacin ligada presentada con anterioridad, debido a que
necesario. permite la implantacin de un rbol en nn bloque contiguo de memoria (un arreglo)
Cuan-do se hace esta distincin entre nodos hojas y nodos no hojas, los ltimos en lugar de hacerlo mediante apuntadores que conecten nodos muy separados.
se llaman nodos internos y los primeros nodos externos. La terminologa tambin se Con la representacin secuencial, se asigna un elemento de un arreglo, ya sea
usa con frecuencia, aun cuando se define un solo tipo de nodo. Por supuesto, un que sirva o no para contener un nodo de un rbol. En consecuencia, hay que indicar
apuntador hijo dentro de un nodo interno debe ser identificado como.apuntador a los elementos del arreglo no usados como nodos del rbol nulos o que no existen. Es-
un nodo interno o externo. Hay dos maneras de hacer esto en C. Una tcnica es to se puede realizar mediante uno de dos mtodos. Uno de ellos es asignar a info[p]
declarar _dos tipos diferentes de nodos y apuntadores y usar una unin para nodos un valor especial si nodo pes nulo. Este.valor especial debera ser invlido como el
internos, con cada alternativa conteniendo uno de los dos tipos de apuntadores. La contenido de informacin de un autntico nodo del rbol. Por ejemplo, en un rbol
otra tcnica es retener un solo tipo de apuntador y un solo tipo de nodo, donde el no- _ que contenga nmeros positivos, nn nodo nulo puede indicarse mediante un valor
do es una unin que contiene (si es interno) o no (si es externo) campos apuntadores negativo de info. Otra posibilidad es agregar a cada nodo un campo lgico indicador
d
izquierdo y derecho. Al final de esta seccin se ver un ejemplo de esta ltima tcnica. used. Cada nodo contiene entonces dos campos, info y used. Toda la estructura est
contenida entonces en un nodo del arreglo. used(p), implantada como node[p].
Representacin con arreglo implcito de rboles binarios use~, es TRUE si el nodo p no es un nodo nulo y FALSE s lo cs. info(p) se implanta
. mediante node[p].info. Este ltimo mtodo se usa para realizar la representacin
Hay que recordar de la seccin 5.1, que los n nodos de un rbol binario cuasi-- . secuencial.
completo pueden enumerar,e desde 1 hasta n, de manera que el nmero asignado al

258 Estructuras de datos en 259


M

(a) Dos rboles binarios


o 2 3 4 5 6 7 8

(a)
e

(b) Extensiones cuasi-completas

o 2 3 4 5 6 7 8 9 10 11 12

i
'I
o 2 3 4 5 6. 7 8 9

. (e) Representaciones por medio de arreglos

(b) Figura 5.2.1 Figura 5.2.2

260 Estructuras de datos-e~: 261


Ahora se presenta el programa para encontrar nmeros duplicados en una lista int q;
de nmeros de entrada, as como las rutinas maketree y set!eft, usando la representa,
cin secuencial de rboles binarios. q = 2 * p + 1; I* q es la posicin del hijo *!
I* izquierdo *I
#define NUMNODES 500 if (q >= NUMNODES)
struct nodetype 1 error("desborde del arreglo");
int info; else if (node(qJ.used)
int used; errorC 1insercin invlida")
node[NUMNODES]; else {
main () node[qJ.info x;
node[qJ.used TRUEl
1 I* fin de f *I
int p, g, number;
I * fin de setleft * I
scanf(' 1 %d 11 , &nurnber);
maketree(number); La rutina para setright es similar.
while (scanf("%d", &number) != EOF) { Obsrvese que bajo e,ta implantacin, la rutina maketree inicializa los campos
p = q = D; info y used para representar un rbol con. un solo nodo. Ya no es necesario que
while (q < NUMNODES && node[gJ.used && number != maketree d como resultado un valor, ya que en esta representacin el rbol binario
node[pl .inf nico representado por los campos info y used tiene siempre raz en el nodo O, Esa es
p = q; la razn por la que p se inicializa a O en la funcin principal antes de moverse hacia
if (number < node[pl.info)
abajo .del .rbol. Obsrvese tambin que en esta representacin, siempre se necesita
q 2 * p + 1;
else comprobar que no se ha excedido el rango (NUMNODES) cada vez que alguien se
mueve hacia abajo del rbol.
q 2 * p + 2;
! * fin de while * I
/ * Si el mmero est. en el rbol, es un duplicado i< /

if (number = = node[p].info) Eleccin de una representacin de rboles binarios


prntf("%d es un duplicado\n", nmero)
else if (number < node[pl.info) Qu representacin de rboles binarios es preferible? No hay una respuesta
setleft(p, number); general a esta pregunta. La representacin secuencial es un poco ms simple, aunque
else es necesario asegurarse de que todos los apuntadores estn dentro de los lmites del
setright (p, number);
arreglo. La representacin secuencial ahorra espacio en memoria para rboles que se
} / * fin de while * I sabe son cuasi-completos, pues elimina la necesidad de los campos left, right y father
I * fin de main * I
maketree(x) eincluso no requiere del campo used. Tambin es eficaz en cuanto a espacio para r-
int x; boles que les faltan unos cuantos nodos para ser cuaskompletos o cuando se elimi-
nan de manera sucesiva, nodos de un rbol que originalmente fue cuasi-completo,
int p; aunque entonces podra necesitarse un campo used. Sin embargo, la representacin
node(Oj.info = x; secuencial puede usarse slo en un contexto en el que se requiera un solo rbol, o
node[DJ.used = TRUE; donde el nmero de rboles necesarios y cada uno d sus tamaos mximos se fija de
* el rbol contiene solamente al nodo O */ antemano.
* todos los otros nodos estn vacos */ En contraste, la representacin ligada requiere de campos !eft, right y father
for (p=1; p < NUMNODES; p++) (aunque ya se ha visto que uno o dos de. ellos pueden eliminarse en situaciones
node[pJ.used = FALSE; \/especficas), pero permite un uso mucho ms flexible del conjunto de.nodos. En la
I * fin de maketree *I representacin ligada, un nodo particular puede colocarse en cualquier localizacin
Y en cualquier rbol, m.ientras que en la representacin secuencial un nodo se puede
setleft(p, X)
int p, x;
usar slo si se necesita en una locacin en un rbol especfico. Adems, en la repre-
sentacin con nodos dinmica, el nmero total de rboles y nodos est limitado
1
exclusivamente por la cantidad de memoria disponible. As, la representacin ligada

262 Estructuras de datos en C'


263
es preferible en la situacin dinmica general de muchos rboles de forma imprede- if (tree != NULL) {
cible. intrav(tree->left); !* recorriendo el subrbol izquierdo */
El programa para encontrar duplicados es una buena explicacin de los com- printf( 11 %d\n 11 , tree->info); I * visitando la raz *
intrav(tree->right); !* recorriendo el s.,ubrbol derecho */
promisos implicados. El primer programa presentado utiliza la representacin liga-
I* fin de if *I
da de rboles binarios. Requiere de campos left y right, adems de info (el campo I * fin de intrav *
father no se necesit en ese programa). El segundo programa para encontrar dupli-
cados, que utiliza la representacin secuencial slo requiere de un campo adicional, posttrav(tree)
used (y ste tambin puede eliminarse si slo se permiten nmeros positivos en la NODEPTR tree;
entrada, de tal manera que un nodo nulo de un rbol pueda representarse mediante {
un valor info negativo especfico). La representacin secuencial puede usarse en este if (tree != NULL) {
ejemplo, porque se requiere un solo rbol. post tra v ( tree- >lef t) ; / *recorriendo el subrbol izquierdo*
Sin embargo, el segundo programa podra no funcionar para tantos casos de posttrav(tree->right); I* recorriendo el subrbol derecho *I
entrada como el primero. Por ejemplo, supngase que la entrada est en orden as- printf("%d\n 11 , tree->info); I* visitandolaraz *I
I* fin deif *I
cendente. Entonces el rbol formado por cualquiera de los programas tiene todos los
I * fin de posttrB.v * /
subrboles izquierdos nulos (se invita al lector a verificar que se sea el caso median-
te la simulacin del programa para tal entrada). En ese caso, los nicos elementos de,/
info que estn ocupados en la representacin secuencial son O, 2, 6, 14 y as sucesiva,, Se invita al lector a simular la accin de estas rutinas sobre los rboles de las figuras
mente (cada posicin es el doble ms dos de la anterior). Si el valor de NUMNODES 5.1.7 y 5.1.8.
se mantiene en 500, se puedert acomodar como mximo 16 nmeros ascendentes disc: Por supuesto, las rutinas pueden escribirse de manera no recursiva para ejecu-
tintos (el ltimo estar en la posicin 254). Esto puede contrastarse con el programa tar la insercin Y la eliminacin necesarias de manera explcita. Por ejemplo, la
que usa la representacin ligada, en el cual se pueden acomodar hasta 500 nmeros, siguiente es una rutina no recursiva para recorrer un rbol binario en orden:
distintos en orden ascendente antes de exceder el espacio. En el resto del texto, a me,;
nos que se indique otra cosa, se asume el uso de la representacin ligada de un rbol #define MAXSTACK 100
binario.
intrav2(tree)
Recorridos de rboles binarios en C NODEPTR tree;
{
Es posible implantar el recorrido de rboles binarios en C por medio de rutinas struct stack
recursivas que reflejen las definiciones de recorrido. Las tres rutinas en C: pretav, int top;
intrav y posttrav, imprimen los contenidos de un rbol binario en preorden, orden y NODEPTR item[MAXSTACKJ;
postorden, respectivamente. El parmetro de cada r\ltina es un apuntador al nodo } s;
NCDEPTR p;
raz de un rbol binario. Se usa la representacin dinmica de los nodos para Un"
rbol binario: s.top = -1;
p = tree;
pretrav(tree)
NODEPTR tree;
(I * descender por las ramas izquierdaS tanto como sea posible, *I
{ I* . guardando los apuntadores a los nodos en el camino *!
if (tree != NULL) ( while (p != NULL) {
printf( 11 %d\n 11 , tree->info); I* push (s, p);
pretrav(tree->left); !"' recorriendo el subrbol iiq p = p~>left;
pretrav(tree~>right); I * recorriendo el subrbol dere. I * fin de while * !
l*ndeif *I I* verificar si se termin, * /
I* fin de pretrav * I if (!empty(s)) { / _ *'
~ / j I

intrav(tree) I * en este punto ~I subrbol izquierdo est vaco *


p = pop(s); ) ;
NODEPTR tree;
{ prin tf ( 1%d \ n u, p- >in fo) ; / * visitando la rdz */

264 Estructuras de datos en 265


p "" p- >r i g h t; / * recorriendo el su_brbol derecho */ nacin de la pila en un recorrido no recursivo, aun cuando no est disponible un
I* fin de if *! campo father.
while ( !empty(s) && p != NULL);
! * fin de intrav2 *I
Arboles binarios hebrados
Se deja como ejercicio al lector la escritura de rutinas para recorrer un rbol bi-
Recorrer un rbol binario es una operacin comn, y sera til encontrar un
nario en postorden y preorden, as como recorridos no recursivos, usando la repre~
todo ms eficaz para implantar el recorrido. Examnese la funcin intrav2 para
sentacin secuencial.
:scubrir la razn por la cual se necesita una pila. La pila se vaca cu~ndo P ~s igual
intrav e intrav2 representan un excelente contraste entre una rutina recursiva y a NULL. Esto ocurre en uno de dos casos. En uno, se abandona el ciclo wh1le de~-
su opuesta, la no recursiva. Si se ejecutan ambas, la recursiva intrav se ejecuta en ge~
pus de haberlo ejecutado una o ms veces. Esto implica que el programa h~ recorn-
neral con mucha ms rapidez que la no recursiva intrav2. Esto se opone a la acepta-
do hacia abajo ramas izquierdas hasta encontrar un apuntador NULL, pomendo un
da "sabidura popular" de que la recursin es ms lenta que la iteracin. La causa
apuntador a cada nodo cuando los pasa. As, el elemento tope de la pila e~ ~l valor
primaria de la ineficacia de intrav2, tal como est escrita, son las llamadas a push,
de p antes de que ste se convierta en NULL. Si se guarda ~n apu~tador a~x1ltar q un
pop y empty. Aun cuando el cdigo para esas funciones est insertado en lnea paso atrs de p, el valor de q puede usarse de manera directa sm necesidad de ex-
dentro de intrav2, sta ser ms lenta que intrav a causa de .las verificaciones de des-
traerlo de la pila. ; .
bordamiento y desbordamiento negativo, a menudo innecesarias, qve se incluyen en
El otro caso en el que pes NULL es aqul en el que el ciclo while se salta por
ese cdigo.
completo. Esto ocurre despus de alcanzar un nodo con un subrbol derecho vaco,
Sin embargo, aun cuando se eliminen las verificaciones mencionadas, intrav
cuando se ejecuta la instruccinp = p - > right, y se regre~a p~ra repetir el cuerp?
es ms rpida que'intrav2 si .se usa un compilador que ejecu_te el proceso recursivo
del ciclo do while. En este punto, ya se habra perdido el cammo s1 no fuese por la pi-
con eficacia! La eficacia de la recursividad est _dada en este caso por un nmero de
la cuyo tope apunta al nodo cuyo subrbol izquierdo se acaba de recorrer. Sin em-
factores:
bargo, supngase que un nodo con un subrbol derecho vaco contiene en su cam~o
right un apuntador al nodo que estara en el tope de la pila de ese punto del algorit-
No hay recursividad "extra" como sucede al calcular los nmeros de Fibonac-
mo (esto es, un apuntador a su sucesor en orden) en lugar de conte?':r un apuntador
ci, donde se vuelven a calcular por separado f(n - 2) y f(n - l), aunque el
NULL. Entonces ya no se necesitara la pila, pues el ltimo nodo v1S1tado durante el
valor de f(n - 2) se utiliza para encontrar el de f(n - 1).
recorrido de un subrbol izquierdo apunta directamente a su sucesor en orden. Un
La pila de recursin no puede eliminarse por completo como ocurre cuando se apuntador de este tipo se cohoce como hebra y debe diferenciarse de un apuntador
calcula la funcin factorial. As, el apilamiento y desapilamiento de la recur- en el rbol que se usa para ligar un nodo a su subrbol derecho o izquierdo.
sin interconstruida es ms eficaz que la versin programada. (En muchos sis- ta figura 5.2.3 muestra los rboles binarios de la figura 5.1.7 con hebras que
temas, el apilamiento puede realizarse mediante e! incremento del valor de un remplaza a apuntadores nulos NULL en los nodos con subrboles derechos vacos.
registro que apunte al tope de la pila y moviendo todos los parmetros dentro Las hebras estn dibujadas con lnea punteada para diferenciarlas de los apuntado-
de una nueva rea de datos con un simple movimiento de bloques. El progra- res al rbol. Advirtase que el nodo de la extrema derecha de cada rbol tiene an un
ma controlado de apilamiento, tal y como se implant, requiere de asigna- apuntador derecho NULL, ya que no tiene sucesor de en orden. Los rboles de este
ciones e incrementos individuales.) tipo se llaman rboles binarios enhebrados a la derecha. . .
No hay parmetros o variables locales extraos, como los hay, por ejemplo, en Para implantar un rbol binario enhebrado a la derecha mediante la 1mplant~-
algunas versiones de la bsqueda binaria. El manejo automtico de la pila para cin dinmica con nodos de un rbol binario, se incluye en cada nodo un campo lo-
recursin no inserta en la pila ms variables que las necesarias. gico extra, rthread, para indicar si su apuntador derecho es o no es una he?ra. Para
ser consistentes, tambin se asigna TRUE al campo rthread del nodo del arbola la
En los casos en que la recursin no implica tal sobrecarga, como en un recorri- extrema derecha (esto es, el ltimo nodo en el recorrido en orden del rbol)_, aunque
do en orden se aconseja al programador usar la recursin de manera directa. su campo right permanece como NULL. En consecuencia, un nodo se defme como
Las rutinas para recorridos presentadas se derivan directamente de las defini- sigue (hay que recordar que se est suponiendo que no existe ningn campo father):
ciones de los mtodos de recorrido. Esas definiciones estn planteadas en trminos
de los hijos izquierdo y derecho de un nodo y no hacen referencia al padre. Por tal struct nodetype {
razn, ambas rutinas, la recursiva y la no recursiva, no requieren de un campo int info;
fa/her y no lo aprovechan aun cuando est presente. Como se ver ms adelante, la struct nodtype *left; ! * apuntador al hijo izquierdo *I
struct nodetype *right I * apuntador al hijo derecho *I
presencia de un campofa1her permite desarrollar algoritmos de recorridos no recur-
sivos sin usar una pila. Sin embargo, primero se examina una tcnica para la elim,i-

266 Estructuras de datos en C Arboles 267


int rthread; I* rthread es TRUE si right Ahora se presenta una rutina para implantar el recorrido en orden de un rbol
I* es NULL o una hebra binario enhebrado a la derecha,
!* diferente de NULL
intrav3(tree)
typedef struct nodetype *NODEPTR;
NODEPTR tree;
{
NODEPTR p, q;
p = tree
do {
q = NULL;
while (p ! = NULL) ! * recorrido de la rama izquierda *!
q = p;
p = p->left;
! * fin de while * !
if ( q != NULL)
printf.( 11 %d \n 11 , q->info)
/) '1 p = q->right;
while (q->rthread && p != NULL)
printf( 11 %d\n 11 , p->info);
1 1
\ q = p;
1
1
1 p = p->right;
\
'\ 1
1
1
\
/ * fin de while *I
I
1
} /* fin deif *I
/
while (q ! = NULL)
/
/
\
' / ' /
!* fin de intrav3 * !

En un rbol binario enhebrado a la derecha, el sucesor de en orden de cual-


quier nodo puede encontrarse con eficacia, Dicho rbol tambin puede construirse
de manera simple, A continuacin se presentan las rutinas maketree, setleft y
setright. Supngase que cada nodo tiene campos info, left, right y rthread.

NODE~TR maketree(x)
int x;
{
NODEPTR p;
1
1

1
1 p = getnode();
1
1
1 p->info = x;
1
1 1 p->left = NULL;
1 1
I'
p->right = NULL;
1
1 1 p->rthread = TRUE;
1 1 return(p);
1
1
1 / *. fin de maketree * !
1
1 /
\ I '- / setleft(p, x-)
' /
NODEPTR p;
Figura 5.2.3 Arboles binarios enhebrados a fa derecha. int x;
{

268 Estructuras de datos en C Arboles 269


intrav,(tree)
NODEPTR q;
int tree;
{
if (p == NULL) int p, q;
error("insercn no efectuada") p = tree;
alse if (p->left != NULL) do {
error( 11 insercn no vlida"); I * descender por el rbol manteniendo el eslabn q detrs de p */
else { q = o;
q = getnode(); while (p != O) {
q->info x; q = Pi
p->left = q; p = node[pJ.left;
q->left = NULL; ) I * fin de while * I
/ el sucesor en orden de node (q) es node (p) *I if ( q ! = O) { / * verificar si se termin */
q->right = p; printf(' 1 %d\n'', node[qJ.info);
q->rthread = TRUE; p = node[q).right;
!* fin deif *I while,(p < O) {
!* fin de setleft * / q " -p;
printf( 11 %d\n 11 , node[q].info);
setright(p, x) p = node[q].right;
NODEPTR p; I * fin de while * I
int x; ! * fin de if * !
{ ! * recorrer el subrbol derecho * !
NODEPTR q, r; while (q ! = O);
I* fin de nfrav4 *!
if (p == NULL)
error{"nsercin no efectuada"); En la representacin secuencial de rboles binarios, el campo used indica las
else if (!p->rthread) hebras por medio de valores positivos o negativos. Si i representa un nodo con un hi-
error("insercin invlida11 ) jo derecho, node[i].used es igual a!, y su hijo derecho est en 2 * i + 2. Sin embar-
else { go, si i representa un nodo sin hijo derecho, node[i] .used contiene el nmero negati-
q = getnode(); vo con valor absoluto igual al ndice de su sucesor en orden. (Obsrvese que el uso de
q->info = x;
/* guardar el sucesor en' Orden de node (p) */ nmeros negativos p~rmite distinguir un nodo con un hijo derecho de un nodo cuyo
~r = p->right;
sucesor en orden sea la raz del rbol.) Si i es el nodo a la extrema derecha del rbol,
p->right = q; de tal manera que no tiene sucesor en orden, node[i] .used puede contener el valor
p->rthread = FALSE; especial + 2. Si i no representa un nodo, node[i].used es O. Se deja como ejercicio al
q->left = NULL; lector la implantacin de algoritmos de recorrido para esta representacin.
I * El sucesor en orden de node (q) es el sucesor previo de * / Un rbol binario enhebrado a la izquierda puede dfinirse de manera similar
!* node(p) *I como el rbol en el que cada apuntador izquierdo NULL se altera para contener una
q->right = r; hebra hacia el nodo antecesor en orden. Un rbol binario enhebrado se puede definir
q->rthread = TRUE; entonces como el rbol binario que est tanto a la derecha como a la izquierda.
!* fin de else *I Sin embargo, los rboles enhebrados a la izquierda no tienen las ventajas que los
I * fin de setrght * I
enhebrados a la derecha.
Tambin pueden definirse los rboles binarios ptehebrados a la derecha y a la td
En la implantacin con arreglo ligada, una hebra puede representarse median- izquierda, en los que los apuntadores derechos e izquierdos de nodos con valor
te un valor negativo de node[p) .right. El valor absoluto de node[p).right es el ndice NULL son sustituidos respectivamente por los sucesores y antecesores de preorden
de sucesor de en orden de node[p] en el arreglo node. El signo de node[p].right indi- del nodo correspondiente. Un rbol binario prehebrado a la derecha puede ser re-
ca si su vlor absoluto representa una hebra (menos) o un apuntador a un subrbol corrido de manera eficaz en preorden sin usar una pila. Un rbol binario enhebrado
no vaco (ms). Bajo esta implantacin, la rutina sjguiente recorre un rbol binario a la derecha tambin puede recorrer en preorden sin usar una pila. Los algoritmos de
enhebrado a la derecha en orden. A manera de ejercicio se dejan las rutinas ma- recorrido se dejan como ejercicio para el lector.
ketree, setleft y setright para las representaciones con arreglo ligadas.

Arboles 271
270 Estructuras de datos en C
Recorrido por medio de un campo father excepto en que, en preorden, se visita un nodo.slo cuando se alcanza descendiendo
del rbol y en postorden, cuando el hijo derecho es NULL o cuando se alcanza
Cuando cada rbol de nodos contiene un campo father, no se necesitan ni despus de regresar del hijo derecho. Los detalles se quedan como ejercicio para el
hebras ni pila para el recorrido no recursivo. En lugar de ello, cuando el proceso de lector.
recorrido alcanza un nodo hoja se puede usar el campo father para ascender por El recorrido que usan los apuntadores father al regresar es menos eficaz en
atrs al rbol. Cuando se alcanza node(p) desde un hijo izquierdo, su subrbol cuanto a tiempo que el recorrido de un rbol enhebrado. Una hebra apunta en forma
derecho an debe recorrerse; por lo tanto, el algoritmo procede hacia right(p), directa al sucesor de un nodo, mientras que en un rbol no enhebrado quiz tenga
Cuando se alcanza node(p) desde su hijo derecho, ya se han recorrido sus dos subr- que seguirse una serie de apuntadores father para alcanzar ese sucesor. No es fcil
boles y el algoritmo retrocede hacia father(p). La siguiente rutina implanta este comparar la eficacia concerniente al tiempo del recorrido basado en la pila con el del
proceso para el recorrido en orden. recorrido basado en el padre, pues el primero incluye la sobrecarga de apilamiento y
desapilamiento.
Este algoritmo de regreso sugiere tambin una tcnica de recorrido no recursi-
intravS(tree)
NODEPTR tree; vo sin pila para rboles no enhebrados, aun cuando no existe el campo father. La
{ tcnica es simple: slo hay que invertir el apuntador son cuando se desciende del r-
NODEPTR p, q; bol para que pueda usarse para encontrar un camino de regreso para ascender. En
este camino de regreso, se restaura al apuntador su valor original.
q = NULL; Por ejemplo, en intrav5, se puede introducir una variable f para guardar un
p = tree; apuntador al padre de node(q). Las instrucciones:
{while ( p ! = NULL) .q p;
q = p; p p->left;
p = p->left;
I * fin de while *I en el primer ciclo while pueden remplazarse por
if (q != NULL) {
printf( 11 %d\n 11 , q->info); f q;
p = q->right; q p;
I* fin deif *I p p->left;
while (q != NULL && p == NULL) { i f (p != NULL)
do { q->left = f;
/* node (q) no tiene hijo derecho. Regresar hasta que
/* se encuentre un hijo izqu.erdo o la raz del rbol Esto modifica el apuntador izquierdo de node(q) para que apunte al padre de
p = q; node(q) cuando se avanza a la izquierda en el camino de descenso [advirtase que p
q = p->father; apunta al hijo izquierdo de node(q), de tal manera que no se pierde el camino]. La
while t!isleft(p) && q != NULL);
i f (q != NULL) {
instruccin,
printf( 11 %d\n 11 , q~>info);
p = q->right; p = q>right;
f* fin de if *!
que aparece dos veces, puede remplazarse por
} /* fin de while *f
while (q ! = NULL);
p = q->righl;
I * fin de intrav5 *I if (p !- NULL)
q->right = f; ,;,

Obsrvese que se escribi isleft(p) en lugar de p - > isleft porque no es nece-


sario un campo isleft para determinar si nc:de(p) es un hijo izquierdo o derecho; se para modificar de manera similar el apuntador derecho de node(q) de manera que
puede verificar simplemente si el nodo es el hijo izquierdo del padre. apunte a su padre cuando se avanza a la derecha en el camino de descenso. Por lti-
En este recorrido en orden se visita un nodo [printf("%d \ n", q - > in/o)] mo, las instrucciones
cuando se reconoce al hijo izquierdo como NULL o cuando se alcanza ste despus
p q;
de regresar del hijo izquierdo. Los recorridos en preorden y postorden son similares
g p->father;

272 Estructuras de datos en C Arboles 273


en el ciclo interno do-whi/e pueden remplazarse por

p = q;
q = f;
if (q != NULL && isleft(p)) {
f left(q);
left(q) = p;
}
else {
f = right(q);
right(g) = p;
I* fin de i *I

para seguir un apuntador modificado que asciende por el rbol y restituye el valor
del mismo para que apunte a su hijo derecho o izquierdo segn se necesite.
Sin embargo, ahora se requiere de un campo isleft, ya que la operacin isleft
no puede implantarse 11tediante un carppo Jather que no existe. Este algoritmo tam-
poco puede usarse en un ambiente de usuarios mltiples si varios usuarios necesitan
accesar al rbol simultneamente. Cuando un usuario recorre el rbol y modifica
temporalmente los apuntadores, otro usuario no podr usarlo como una estructura
coherente. Se necesita algn tipo de mecanismo de cierre para asegurar que nadie
ms use el rbol mientras se invierten los apuntadores. Figura 5.2.4 Arbol binario que representa a 3 + 4 * (6 - 7)/5 + 3.

Arboles binarios heterogneos


Escribase ahora una funcin en C, de nombre eva!bintree, que acepte un apun-
Con frecuencia, la informacin contenida en los diferentes nodos de un rbol tador a un rbol de este tipo y d como resultado el valor de la expresin representa-
binario no es del mismo tipo. Por ejemplo, en la representacin de una expresin bi- da por el rbol. La funcin evala de manera recursiva los subrboles izquierdo y
naria con operandos numricos constantes, tal vez se desee usar un rbol binario derecho y luego aplica el operador de la raz a los dos resultados. Aqu se usa la fun-
cuyas hojas contengan nmeros pero cuyos nodos que no son hojas contengan carac- cin. auxiliar oper(symb, opnd!, ojmd2) presentada en la seccin 2.3. El primer pa-
teres que representen operadores. La figura 5.2.4 ilustra un rbol binario de este rmetro de oper es un carcter que representa un operador y los dos ltimos par-
tipo. _ metros son nmeros reales que son los operandos. La funcin oper regresa el resul-
Para representar dicho rbol binario en C, se puede usar una unin que repre- tado de aplicar el operador a los dos operandos.
sente la porcin de informacin del nodo. Por supuesto, cada nodo del rbol debe
contener en s un campo que indique el tipo de objeto que contiene su campo info.
float evalbintree (tree)
NODEPTR tree;
#define OPERATOR o {
#define OPERAND 1 float opnd1i opnd2;
struct nodetype { char symb;
short int utype; /O OPERATOR or OPERANDO/
union { if (tree->utype == OPERAND) /* la expresin es un operando */
char chinfo; /O nico *I
float numinfo; return (tre->numinfo);
info; I* tree->utype ==
OPERATOR *I
struct nodetype *left; / * evaluar el subrbol izquierdo * /
struct nodetype *right; opnd1 evalbintrae(tree->left);
}; I * evaluar' el subrbol derecho i: /

typedef struct nodetype *NODEPTR; opnd2 evalbintree(tree->right);

274 Estructuras de datos en C


Arboles 275
symb = tree->chinfo; I* exfraer el operador movimiento del disco del tope del poste x al poste y,- sea z el tercer poste que no es
J * aplicarlo y regresar el resultado ni el origen ni el destino del nodo nd. Entonces left(nd) representa un movimiento
evalbintree = oper(symb, opndl, opnd2); del disco tope del poste x al poste z y right(nd) uno del poste z al poste y. Dibujar
/ * fin de evalbintree * I rboles de solucin muestra cmo se describi previamente para n = 1, 2, 3 y 4 y
mostrar que un recorrido en orden de tal rbol produce la solucin al problema de
En la seccin 9. l se analizan mtodos adicionales de implantacin de estructu- las Torres de Hanoi.
ras ligadas que contienen elementos heterogneos. Obsrvese tambin que, en este b. Escriba un procedimiento recursivo en C que acepte un valor n y genere y recorra
ejemplo, todos los nodos operando son hojas y todos los nodos operadores el rbo! como se discuti previamente.
c. Debido a que el rbol est completo, se puede guardar en un arreglo de tamao
no-hojas.
zn -1. Muestre que los nodos del rbol se pueden almacenar en el arreglo de tal
manera que un recorrido secuencial del mismo produzca el recorrido en orden del
rbol como sigue: la raz del rbol est en la posicin.zn- 1 -1; para cualquier
EJERCICIOS nivelj, el primer nodo en ese nivel est en la posicin zn- l - j -1 y cada nodo su-
cesivo en el nivelj est zn-J elementos ms all del elemento previo de ese nivel.
d. Escriba un progra,ma no recursivo en C para crear el arregle, descrito en-la parte e
5.2.1. Escriba una funcin en C que acepte un apuntador a un nodo y que d como resulta-
y mostrar que el paso secuencial a travs del arreglo produce de hecho la soluc:n
do TRUE si ese nodo es la raz de un rbol binario vlido y FALSE en caso contrario.
deseada.'
5.2.2. Escriba una funcin en C que acepte un apuntador a un rbol binario y un apuntador e. Cmo se podra extender el programa anterior para incluir en cada nodo el n-
a un nodo del mismo y que d como resultado el nivel del nodo del rboL mero del disco que se est moviend_o?
5.2.3. Escriba una funcin e~ C que acepte un apu-itador a un rbol binarioy d como s.2.10 . En la seccin 4.5 se introdujo un mtodo para representar tina lista doblemente ligada
resultado un apuntador a un nuevo rbol binario que sea la imagen especular (esto con un solo campo apuntador en cada nodo manteniendo su valor como la "o"
es, todos los su_brboles_derechos son ahora s_ubrboles izquierdos y viceversa) del pri- exclusiva de los apuntadores al antecesor y sucesor del nodo. Algo similar puede ha-
mero. cerse con un rbol binario. si se mantiene en.cada nodo un campo como la "o" exclu-
5.2.4. Escriba funciones en C que conviertan un fbol binario implantado mediante la siva de apuntadores al nodofather e hijo izquierdo [llmese a este campofleft(p)] y
representacin ligada con arreglo con slo un campofather (en el que el hijo izquier:. otro_ campo en el nodo como la ''o'' exclusiva de los apuntadores a los nodosfather e
- do del campo' father contiene el valor negativo del apuntador a su padre y el hijo de- hijo derec;ho [llmese a este campo fright(p)].
recho de _dicho padre contiene un apuntador a su padre) a su representacin que usa a. Dados father(p) y f{eft(p), muestre cmo. cakular left(p).
c~mpos left y right y viceversa. Dadosfather(p) y fright(p), muestre cmo calcular right(p).
5.2.5. Escriba un program_a en C para realizar-.el-.siguiente experime_nto: la generacin de 100 b. Dadosf{eft(p) y /eft(p), muestre cmo calcular father(p).
nmeros aleatorios. Cada vez que se genere. un nm_erO, insrtelo en un rbol de bs- Dadosfright(p) y right(p) muestre cmo calcular father(p).
q:ueda binaria .ini_cialm_ente vado. Una:_v_ez que se inser.tn._Ios.100 _nmeros, imprima el c. Supngase que un nodo contiene slo campos info,jleft,fright e isleft. Escriba al-
nivel de la hoja con nivel mximo y.el de la hoja cofl fiivel _mnir{o. Repita este proce_~ goritmos para los recorridos en preorden, en orden y postorden de un rbol bina-
sO 50 veces. Imprima una tabla con un conto acerCa de cuntas de las 50 veces hubo rio, dado un apuntador externo a la raz del rbol sin usar una pila y sin modificar
diferencia entre el nivel de hoja mxirr(fy rnni~o "de :, l, 2, 3,' et:tera. ningn campo.
5'.2.6. Escriba rutinas en C para recorrer un rbol binario en postorden y preorden. d. Puede eliminarse el campo isleft?
5.2.7. Implante el recorrido en orden. maketre?, setleft y setrght para rboles binarios 5.2.11. El ndice de un libro de texto consta de trminos principales colocados en orden alfa-
enhebrados a la derecha bajo la representacin secuencial. btico. Cada uno de ellos va acompafiado por un conjunto de. nmero.s .de pgina y un
5.2.8. Escriba funciones en C para crear un rbol binario determinado a: conjunto de subtrmi.nos_. Los subtrminos estn impresos en lineas sucesivas que
a. los recorridos en preorden y en orden de ese rbol siguen al trmino Principal y dispuestos en orden alfab,tico dentx: d_e trffiin~ princi-
b. los recorridos en preorden y postorden de ese rbol pal. Cada subtrmino va acompaado de un conjunto de, n(lmros de. pgina.
Disee una estructura de datos Pa_ra represeritar un ndic cmo el descrito i,escriba
Cada. funcin debe aceptar dos cadenas de car_acteres como parmetros. El rbol
creado debe contener u solo carcter e'ri Cada nodo. un programa en C para imprimir un ndice de datos_ como sigue. Cada ~n_a de entra-
da comienza con una m (trmino principal) o unas (subtrmino). Una lnea m con-
5.2.9. La solucin al problema de las Torres de Hanoi, paran discos (ver secciones 3.3 y
tiene una m seguida de un trmino principal, seguido por un entero n (O s posible),
3.4). puede representarse mediante un rbo! bin_ario completo de nivel n - 1 como
seguido por n nmeros de pgina en que aparece el trmino principal. Una lneas es
sigue:
similar excepto en que contiene un subtrmino n lugar de un trmino principal. Las
a. Sea que la raz del rbol represente un movim,ientO del d.isco tope del poste from-
lneas de entrad no aparecen en orden particular; excepto por el hecho de qlle cada
peg al poste topeg. (Se desconoce la identificacin'.de. ls_ disCo's que.se estn mo~
subtrmino se considera un subtrmino del ltimo trmino principal que lo precede.
viendo, puesto que hay un solo disco que se puede mover {el del tOpe} de un poste
Puede haber muchas lneas de entrada para un solo trmino principal o subtrmino
a otro.) Si nd es un nodo hoja (en un nivel menor qe n - l)-que representa el
(todos los nmeros de pgina que aparecen en cualquier lnea para un trmino parti-
cular deben imprimirse con el mismo).
276 Estructuras de datos en C Arboles 277
::El mensaje ABACCA se coificara entonces como 010100010000000111010. Una
El ndice debe imprimirse en una linea con un trmino seguido por todas las pginas ' codificacin de este tipo es ineficaz, debido a que se usan tres bits para cada
en las que aparece dicho trmino en orden ascendente. Los trminos principales de- s!mbolo, por lo que se necesitan 21 bits para todo el mensaje. Supngase que se asig-
ben imprimirse en orden alfabtico. Los subtrminos, que tambin deben aparecer e~
na un cdigo de dos bits a cada smbolo, de la siguiente manera:
orden alfabtico, van inmediatamente despus de su trmino principal. Los subtrmi-
nos deben colocarse a cinco columnas del trmino principal.
El conjunto de trminos principales debe organizarse como un rbol binario. Cada-?
Smbolo' Cdigo
nodo del rbol contiene (adems de los apuntadores izquierdo y derecho y el trmino
principal) apuntadores a otros dos rboles binarios. Uno de stos representa el con- A 00
junto de nmeros de pgina en las que aparece el trmino principal; el otro representa B 01
eJ conjunto de subtrminos del mismo. Cada nodo de un rbol binario que representa e 10
un subtrmino contiene (adems de los apuntadores izquierdo y derecho y el propio D ll
subtrmino) un apuntador a un rbol binario que representa el conjunto de nmeros
de pginas en las que aparece el subtrmino.
5.2.12.' Escriba una funcin en C para implantar un mtodo de ordenamiento como el ie ta Entonces el cdigo para el mensaje sera 00010010101100, que requiere nicamente
seccin 5.1 que use un rbol de bsqueda binaria. 14 bits. Por tanto, se dese:, encontrar un cdigo que reduzca al mnimo la longitud
5.2.13. a. Implante una cola de prioridad ascenderte usando U1 rbol de bsqueda binaria delmensaje codificado.
por medio de implantaciones en C de los algoritmos pqinsert y pqmindelete, igual Examnese otra vez el ejemplo anterior. Cada una de las letras By D aparecen
que en el ejercicio 5.1.13. Modificar las rutinas para contar el nmero de nodos del una sola vez en el.mensaje, mientras que la letra A aparece tres veces. Sise escoge un
rbol accesados. cdigo de tal manera que. se le asigne a la A una cadena de bits ms corta que las
b. Use un generador de nmeros aleatorios para verificar la eficacia de la implanta- letrasB y D, la longitud del mensaje codificado ser ms pequea. Esto ocurre por-
cin de una cola de prioridad como sigue. Primero, cree una cola de prioridad con que el. cdigo corto (que representara a la letra A) aparece con ms frecuencia que el
100 elementos mediante la insercin de 100 nmeros aleatorios en un rbol de bs-
largo. De hecho, los cdigos pueden asignarse como sigue:
queda binaria inicialmente vaco. Despus, llame a pqmindelete e imprima el n-
mero de nodos del rbol accesados para encontrar el elemento mnimo, genere lin
nuevonmerq aleatorio y llame apqinsert para insertar el nuevo nmero aleatorio
e imprima el nmero de nodos del rbol accesados en la insercin. Advirtase que Smbolo Cdigo
despus de llamar a pqinsert, el rbol an contiene 100 elementos. Repita el proce..:
so de eliminar/imprimir/generar/insertar/imprimir mil veces. Advertir que el
A o
B l 10
nmero de nodos accesados en la eliminacin tiende a disminuir, niientras que e 10
el nmero de nodos accesados en la insercin tiende a crecer. Explicar dicho com- D lll
portamiento.

UN EJEMPLO: EL ALGORITMO DE HUFFMAN Mediante este cdigo el mensaje ABACCDA se codifica como 0110010101110,
el que ocupa slo 13 bits. En mensajes muy largos que contienen smbolos que apa-
Supngase que se tiene un alfabeto den smb(llos y un largo mensaje compuesto con recen con muy poca frecuencia, los ahorros son sustanciales: Por lo general, los
smbolos del mismo. Se desea codificar el mensaje como una larga cadena de bits (un cdigos no se construyen sobre la base de la frecuencia de caracteres en un mensaje
bit es O o 1) mediante la asignacin de una cadena de bits como cdigo para cada aslado, sino sobre la base de su frecuencia dentro de todo un conjunto de mensajes.
smbolo del alfabeto y la concatenacin de los cdigos individuales de los smbolos El mismo conjunto de cdigos se usa entonces para cada mensaje. Por ejemplo, si
que conforman el mensaje para producir la codificacin del mismo. Por ejemplo, los mensajes consisten en palabras del ingls, podra usarse la frecuencia conocida
supngase que el alfabeto consta de los cuatro smbolos A, B, C y D y que se asignan de ocurrencia de las letras del alfabeto de la lengua inglesa, aunque la frecuencia
cdigos a estos smbolos como sigue: relativa de las letras de un solo mensaje no es necesariamente la misma.
Si se usan cdigos de longitud variable, el cdigo para un smbolo no puede ser
un prefijo del cdigo para otro. Para descubrir por qu, supngase que el cdigo pa-
Smbolo Cdigo
ra el smbolo x, c(x), es un prefijo dd cdigo para otro smbolo y, c(y). Entonces,
A 010 cuando se encuentra c(x) al examinar de izquierda a derecha, no queda claro si c(x)
B 100 presenta ax o si es la primera parte de c(y).
e 000
D 111

Arboles 279
278 Estructuras de datos en C
En el caso del ejemplo anterior, la decodificacin procede examinando una c
dena de caracteres de izquierda a derecha. Cuando se encuentra un O como prim
bit, el smbolo es A; en caso contrario, el smbolo ser B, C o D, y se examina el bf
siguiente. Si el segundo bit es un O, el smbolo es una C; en caso contrario, tiene qu~
ser una B o una D y debe examinarse el tercer bit. Si el tercer bit es un O, el smbol.
es una B, y si es un l, es una D. Tan pronto como se ha identificado el primer
smbolo, se repite el proceso comenzando con el siguiente bit para encontrar el se-
gundo smbolo. c. 2
Esto sugiere un mtodo para desarrollar un esquema de codificacin ptimo,
dada la frecuencia de ocurrencia de cada smbolo en un mensaje. Encontrar los dos
smbolos que aparecen con menos frecuencia. En el ejemplo citado, stos son By D:
El ltimo bit de sus cdigos es diferente: O para B y 1 para D. Combinar estos dos
smbolos dentro de uno solo, BD, cuyo cdigo represente el conocimiento de que el ('}
simbol<1l es una B o una D. La frecuencia de ocurrencia de este nuevo smbolo esla:
suma de las frecuencias de los smbolos que lo constituyen. Entonces, la frecuencia ..
de BD es dos. Hay ahora tres smbolos: A (con frecuencia 3), C (con frecuencia 2) y JHFBDtGCA. 91
BD (con frecuencia 2). Se eligen de nuevo los dos smbolos con menor frecuencia: C{
BD. Los ltimos bits de sus cdigos difieren otra vez el uno del otro: Opara C y 1 pa,
ra BD. Los dos smbolos se combinan entonces en uno solo, CBD, con frecuencia 4.;
Ahora slo restan dos smbolos: A y CBD. Estos se combinan en un solo smbolo
ACBD. El ltimo bit de sus cdigos para A y CBD difieren uno del otro: Opara A y
1 para CBD .
.El smbolo ACBD contiene todo el alfabeto; a ste se le asigna la cadena de bits HFBD. 23 E. 25
nula de longitud Ocomo cdigo. Al principio de la decodificacin, antes de que nin-
gn bit haya sido examinado, es seguro que todo smbolo est contenido en ACBD.
A los dos smbolos que componen A CBD (A y CBD) se les asignan los cdigos Oy l,
D. t2 A. 15
respectivamente. Si se encuentra un O, el smbolo codificado es una A; si se en-
cuentra un 1, es una Cuna B o una D. De manera anloga, se asigna a los smbolos
que constituyen CBD (C y BD) los cdigos l O y 11, respectivamente. El primer bit
indica que el smbolo es uno de los que constituyen a CBD; el segundo, si se trata de
C o de BD. Despus se asignan los cdigos 110 y 111 a 1os que componen BD (By .
D). Mediante este proceso, se asignan cdigos ms cortos a los smbolos que apare-
cen con msfrecuencia que a los que apar,cen menos .
. La accin de combinar dos smbolos en uno sugiere el uso de un rbol binario.
Cada nodo del rbol representa un smbolo, y cada hoja, un smbolo del alfabeto
original. La figura 5.3. la muestra el rbol binario construido por medio del ejemplo (h}
previo. Cada nodo de la ilustracin contiene un smbol.o y su frecuencia. La figura
5.3.1 b muestra el rbol binario construido mediante este mtodo para el alfabeto y
la tabla de frecuencias de la figura 5.3.lc. Tales rboles se !laman rboles de Huff Frecuencia Cdigo Smbolo Frecuencia Cdigo Smbolo Frecuencia Cdigo
man por el descubridor de este mtodo de codificacin.
Ya que est elaborado el rbol de Huffman, puede construirse el cdigo para 15 JJ 1 D 12 01 t e 6 1100
6 OIOt E 25 10 1 01000
cualquier smbolo del alfabeto comenzando en la hoja que represente ese smbolo y 7 1101 F 4 01001 I 15 00
subiendo hacia la raz. El cdigo se inicializa a null. Cada vez que se asciende a una
rama izquierda, se agrega O al principio del cdigo y cada vez que se asciende a /el
una derecha, se agrega I al principio del cdigo.
Figura 5,3.1 Arboles de Huffman

280 Estructuras de datos en C Arboles 281


El algoritmo de Huffman code[] = la cadena de bits nula;
while (p ! = root) {
Las entradas del algoritmo son n, el nmero de smbolos del alfabeto original, I * ascender por el rbol *I
y frequency, un arreglo del tamao de n pr lo menos, de tal manera que ii (isleft(p))
frecuency[i] es la frecuencia relativa del i-simo smbolo. El algoritmo asigna valo- code[ il O seguido de code{i];
else
res a un arreglo code del tamao den por lo menos, de manera que code[i] contiene
code( i J 1 seguido de cocle[i];
el cdigo asignado al i-simo smbolo. El algoritmo tambin construye un arreglo p =
father(p);
position del tamao den por lo menos, por lo que position[i] apunta al nodo que I *
n de while I *
representa el i-simo smbolo. Este arreglo es necesario para identificar el punto en I* fin defor *I
el rbol desde el que se va a comenzar al construir el cdigo para un smbolo particu-
lar del alfabeto. Construido el rbol, la operacin isleft introducida antes puede
usarse para determinar si Oo 1 debe colocarse al frente del cdigo cuando se asciende Un programa en e
en el rbol. La porcin nfo de un nodo del rbol contiene la frecuencia de ocurren-
cia del smbolo representado por dicho nodo. Obsrvese que el rbol de Huffman es estrictamente binario. As, si hay n
Se usa un conjunto rootnodes para guardar apuntadores a la raz de rboles smbolos en el alfabeto, el rbol de Huffman (que tienen hojas) puede representarse
binarios parciales que an no son subrboles izquierdos o derechos. Como este con- mediante un arreglo de .nodos de tamao 2n - 1. Como la cantidad de memoria
junto se modifica por medio de la eliminacin de elementos con frecuencia mnima, que se necesita para el rbol es conocida, sta puede asignarse de antemano en un
la combinacin de stos y la posterior reinsercin del elemento combinado dentro arreglo de nodos,
del conjunto se implanta como una cola de prioridad ascendente de apuntadores, or- En la construccin del rbol y la obtencin de los cdigos, slo es necesario
denados segn el valor del campo info de los nodos que son blanco de los apuntado- guardar una liga de cada nodo a su padre y una indicacin de cundo cada nodo es
res. Las operaciones pqinsert, se usan para insertar un apuntador dentro de la cola un hijo derecho o izquierdo; los campos left y right son innecesarios.As, cada nodo
de prioridad y las pqmindelete, para eliminar el apuntador al nodo con valor info contiene tres campos: fa/her, isleft y freq. Jather es un apuntador al padre del nodo.
ms pequeo de la cola de prioridad. Si el nodo es la raz, su campo father es NULL. El valor de lsleft es TRUE si el nodo
El algoritmo de Huffman se puede esbozar como sigue: es un hijo izquierdo, y FALSE en caso contrario. freq (que corresponde al campo
info del algoritmo) es la frecuencia de ocurrencia del smbolo representado por el
I* inicializar el conjunto de nodos de raz */ nodo en cuestin.
rootnodes = la cola vaca de prioridad ascendente ; Se reserva memoria para el arreglo node con base en el nmero mximo po-
I* construir un nodo para cada smbolo */ sible de smbolos (una constante maxsymbs) y no en el nmero real de smbolos n.
for ( i = O; i < n; i++) { As el arreglo node, que sera de tamao 2n - !, se declara de tamao 2 *
p =
maketree(frequency[i]); MAXSYMBS ~. l. Esto significa que se desperdicia un poco de espacio. Por su-
= p, = p; I * un apuntador a la hoja que contiene el i~simo
position{i} puesto, n mismo puede asignarse como una constante y no como una variable, pero
I* smbolo
entonces el programa deber modificarse cada vez que difiera el nmero de
pqinsert(rootnodes, p);
!* fin defor *!
smbolos. Los nodos tambin se pueden representar por medio de variables dinmi-
wb.ile (roofnodes contenga ms de un ele,~ento) cas sin desperdiciar espacio. No obstante, se presenta la implantacin con arreglo
pt pqmindelete(rootnodes); ligada. (Tambin se puede dar como entrada el valor de n y asignar arreglos del ta-
p2 = pqmindelete(rootnodes); mao apropiado usando malloc de manera dinmica durante la ejecucin. Entonces
! * combinar pl y p2 como ramas de un rbol nico */ no desperdiciara espacio usando una implantacin con arreglo.)
p = maketree(info(p1) + info(p2)); Mediante la implantacin con arreglo ligada pueden reservarse desde node[O]
setleft(p, p1); hasta node[n - !] para las hojas que representan los n smbolos originales del alfa-
setright(p, p2); beto y node[n] hasta node[2 * n - 2] para los n ~. 1 nodos que no son hojas re-
pqinsert(rootnodes, p); queridos por el rbol estrictamente binario. Esto significa que el arregl position no "'
/* fin de while *I se requiere como gua para los nodos hoja que representan los n smbolos, pues se
sabe que el nodo que contiene el i-simo smbolo de entrada (donde i va desde O has-
I * Ahora, est construdo el rbol; utilcelo para encontrar cdigos */
root pqmindelete(rootnodes); tan - 1) es node[i]. Si se usara la representacin dinmica con nodos, se requerira
for ( i .~ O; i < n; i++) { del arreglo position.
p = position[iJ;

2112 Estructuras de datos en C Arboles 283


El siguiente programa codifica un mensaje por medio del algoritmo de Huff- I* al nodo raz siguiente *I
man. La entrada consta de un nmero n, que es el nmero de smbolos del alfabeto, I* en la cola de prioridad *I
seguido por un conjunto den pares, cada uno de los cuales consiste en un smbolo y int isleft;
su frecuencia relativa. El programa construye primero una cadena a!ph, que consta };
de todos los smbolos del alfabeto, y un arreglo de cdigo tal que code[i] es el cdigo
del i-simo smbolo en a/ph. Despus, el programa imprime cada carcter, su fre- main ()
cuencia relativa y su cdigo. {
Como el cdigo se construye de derecha a izquierda, hay que definir una struct codetype cd, code[MAXSYMBSl;
struct nadetype node[MAXNODESJ;
estructura codetype como sigue:
int i, k, n, p, p:1, p2, root, rootnodes;
char symb, alph[MAXSYMBSJ;;
#define MAXBITS so
far (i - o; i < MAXSYMBS; i++)
struct codetype alph[il 1 ;
int bits[MAXBITS]; rootnodes = D;
int startpos; I* introducir el alfabeto y las frecuencias */
); scanf( 11 %d", &n);
for ( i O; i < n; i++) {
MAXBITS es el nmero mximo de bits permitidos en un cdigo. Si un cdigo cd es
nulo, cd.startpos es igual. a MAXB!TS. Cuando se agrega a cd un bit bala izquier-
. scanf( 11 %s %d 11 , &sy~b, &node(i);fr~~);
pqinsert(rootnodes, i);
alph[il symb;
da, cd.startpos decrece en 1 y a cd.bits[cd.startpos] se le asigna b. Cuando se
!* fin de for *!
completa un cdigo cd, los bits del cdigo estn en las posiciones cd.startpos hasta
MAXBITS - l, inclusive. I * ahora se construyen:' los rboles */
Un aspecto importante es saber cmo organizar la cola de prioridad de los no- far (p n; p < 2*n--1; p++) {
dos races. En el algoritmo, esta estructura de.datos se represent como una cola de, I* p apunta al siguiente nodo disponible. Obtener los nodos *I
prioridad de nodos con apuntadores. La implantacin de la cols1. de prioridad me- /* raz pl y p2 con las menores frecuencias, *!
diante una lista ligada, como en la seccin 4.2, requerira de un nuevo conjunto de p1 pqmindelete(roatnades);
nodos, cada uno de los cales guardar un apuntador al nodo raz y un campo next. p2 pqmindelete(rootnodes);
Por fortuna, no se usa el campofather de un rn:,do raz, por lo que puede emplearse /* asignar lelt(p) a p l y right(p) a p2 */
para ligar todos los nodos raz dentro de una lista. El apuntador roo/nades podra nade[p1].father p;
apuntar al primer nodo raz de la lista. La lista misma puede estar ordenada o no, node[p1J.isleft TRUE;
dependiendo de la implantacin de pqinsert y pqmindelete. node[p2J.father p;
node[p2J.isleft FALSE;
En el siguiente programase hace uso de esta tcnica, la que implanta el algorit, node[pJ.freq node[p1).freq + node[p2J.freq;
mo que se acaba de presentar. pqinsert(rootnodes, p);
) I* fin de far *I
#define MAX~ITS so /* ahora hay un solo nodo izquierdo *I
#define MAXSYMBS so /* con un campo Jatber vaco *!
#define MAXNODES 99 9 / * MAXNODES es igual a 2'MAXYMBS-l */ root =
pqmindelete(roohlodes);
/ * extraer los cdigos por medio del .rbol *!
struct codetype { far ( i O; i < n; i++) {
int bits[MAXBITS]; I* inicializar code[i] */
int startpos; cd.startpas = MAXBITS;
) ; / * ascender por el rbol * /
struct nodetype p = i;
int freq; while (p ! raat) {
int father; I* if node[p] no es el nodo raz *.I --cd.startpos;
I* father seala al nodo padre si *I if (nade[iJ.isleft)
I* lo es, father apunta *I cd.bits[cd.startposl o;

284 Estructuras de datos en C . Arboles 285


else
cd.bits[cd.startposJ 1; dos para representar un rbol de Huffman y bitcode sea una cadena_de bits. La funcin
p - nodeEpl.father; asigna a msge la decodificacin de Huffman de bitcode.
I * fin de while *J S,3.3, Implante la cola de prioridad- rootnodes como una lista ordenada. Escribir rutinas
for (k - code[il.startpos; k < MAXBITS; k++) 0 pqinsert y pqminde/ete apropiadas.
code[il.bits[kl - cd.bits[kl; 5,J.4. Es posible tener dos rboles de Huffman diferentes para un conjunto de smbolos con
code[iJ.startpos = cd.startpos; determinadas frecuencias? Pruebe que slo existe un rbol como se o d un ejemplo
I* fin de for *f en el que haya dos de stos.
* impresin de resultados * /
!
s.J.5. Defina el rbol binario de Fibonacci de orden n como sigue: Sin = O o n = l, el rbol
far (i = O; i < n; i++)
consta de un solo nodo. Si n > 1, el rbol consta de una raz, con el rbol de Fibona~
printf( 11 \n%c %d 11 , alph[iJ, nodes[i).freq);
nacci de orden n - l como subrbol izquierdo y el rbol de Fibonanacci de orden
for (k - code[il.startpos; k < MAXBITS; k++) n - 2 como subrbol derecho.
printf( 11 %d 11 , code(i] .bits[kJ);
a. Escriba una funcin en C que propdrcione un apuntador al rbo_l binario d_e Fibo-
printf( 11 \n 11 ) ;
nacci de orden n.
I* lndefor *I
b. Tal rbol es estrictamente binario?
I* fin demain -*!
c. Cul es el nmero de hojas en el rbol de Fibonacci de orden n?
d. Cul es la profundidad del rbol de Fibonacci de orden n?
Al lector se le deja el cdigo de la rutina encode(alph, code, msge, bitcode). 5.3.6. Dado un rbol binario/; su extensin se define como el rbol binari e(l)formado de 1
Este procedimiento acepta la cadena alph, el arreglo code construido en el programt1 mediante la adicin de na hoja a cada apuntdot izquierdo y derecho NULL en t. Las
anterior y un mensaje msge, y asigna a bitcode la cadena de bits que codifica el men- hojas nuevas se llaman nodos externos, y los nodos originales (que ahora son nodos no-
saje. . hoja) se llaman nodos internos. e(t) se llama un rbol binario extendido.
Dada la codificacin de un mensaje y el rbol de Huffman usado en la cons- a. Pruebe que un rbol binario extendido es estrictamente binario.
truccin del cdigo, el mensaje original puede recobrarse como sigue. Hay que co- b. Si t tienen nodos, cuntos nodos tiene e(t)?
menzar en la raz del rbol. Cada vez que se encuentre un O, hay que mover hacia c. Pruebe que todas las hojas de un rbol binario extendido son nodos recin agrega-
abajo una rama izquierda, y cada vez que se encuentre un 1, hay que moverse hacia dos.
d. Escriba una rutina.en C que extienda un rbol binario t.
abajo una rama derecha. Repetir el proceso anterior hasta encontrar una hoja. El
e. Pruebe que un rbol estrictamente binario con ms de un nodo es una extensin de
siguiente carcter del mensaje original es el smbolo que corresponde a esa hoja. un solo rbol binario. .
Buscar si se puede codificar 1110100010111011 mediante el rbol de Huffman de la f; Escriba una fu.ncin e.n C que acept'eU:n apuntad~r a un rbol estrictamente bnari
figura 5.3. lb. 11 que contenga ms de un nodo y elimine nodos de t I creando un rbol binario t2
Para ello, es necesario ir de la raz def rbol hacia abajo, hasta sus hojas. Esto de tal manera que 11 = e(/2).
significa que en lugar de los campos father e isleft, se necesitan dos campos /eft y g. Muestre que el rbol binario completo de orden n es la n-sima extensin del rbol
right para guardar los hijos derecho e izquierdo de un nodo particular. El clculo de binario que consiste en un solo nodo.
los campos !eft y right, a partir de los campos/a/her e is!eft, es directo. Otra posibili- 5.3,7. Dado un rbol estrictamente binario ten el que las nhojas estn etiquetadas como no-
dad es construir los valores !eft y right de manera directa a partir de la informacin dos del 1 hasta n, sea level(i) el nivel del nodo i y freq(i) un entero asgnado al nodo i.
Definir la longitud de camino con pesos de t como la suma de freq(i) /eve/(i) sobre
:l
de frecuencia para los smbolos del alfabeto y mediante. una aproximacin similar a
la usada en la asignacin del valor de father. (Por supuesto, parn que los rboles todas las hojas de t.
sean idnticos, los pares smbolo/frecuencia se deben presentar en el mismo orden en a. Escriba una rutina en C para calcular la longitud de camino con peso, dados los
cualquiera de los dos mtodos.) Al lector se le'dejan como ejercicio estos algoritmos, campos freq y father.
b. Muestre que el rbol de Huffman es el rbol estrictamente binario con longitud de
as como el algoritmo de decodificacin. camino con peso mnima.

EJERCICIOS REPRESENTACION DE LISTAS COMO ARBOLES BINARIOS "'


5.3.l. Escriba en Cuna funcin encode(alph, code, msge, bitcode). La funcin acepta la ca-
dena a/ph, el arreglo code producido por el programafindcode en el texto y un mensaje
Hay varias operaciones que pueden ejecutarse con una lista de elementos. Entre es-
msge. El procedimiento asigna a bilcode la codificacin de Huffman de dicho mensaje. tas operaciones estn la suma de un nuevo elemento en el frente o en el final de la lis-
5.3.2. Escriba en Cuna funcin decode(alph, left, right, bitcode, msge), en donde alph sea la ta, la eliminacin del primer o ltimo elemento de una lista, la recuperacin del
cadena producida por el programafindcode en el texto, /eft y right sean arreglos usa- k-simo elemento o del ltimo elemento de la lista, la insercin de un elemento se-
guido o precedido por un elemento dado, la eliminacin de un elemento dado y la

286 Estructuras de datos en C


Arboles 287
eliminacin del antecesor o el sucesor de un elemento dado. La construccin de una.:
lista con elementos dados es una operacin adicional que se usa con frecuencia. A B e
De acuerdo con la representacin que se elija para una lista, algunas opera,
dones pueden o no realizarse con grados variables de eficacia. Por ejemplo, una lis-
ta se puede representar mediante elementos sucesivos en un arreglo o como nodos eri
una estructura ligada. La insercin de un elmento que sigue a un elemento dado es
ms o menos eficaz en una lista ligada (lo que implica modificaciones a unos cuan-
tos apuntadores, adems de la insercin real) pero relativamente ineficaz en un (a

arreglo (pues implica el movimiento de todos los elementos subsecuentes una posi-
cin dentro del arreglo). Sin embargo, encontrar el elemento k-simo de una lista es
mucho ms eficaz en un arreglo (implica slo el clculo de un desplazamiento) que
en una estructura ligada (la que requiere pasar a lo largo de los k. - 1 primeros ele-
mentos). De manera anloga, no es posible eliminar un elemento especfico de una
lista lineal ligada no doble si slo se da un apuntador a dicho elemento, y slo es po-
sible hacerlo de manera poco eficaz en una lista ligada circular no.doble (si se recorre
toda la lista para alcanzar el elemento previo y despus se ejecuta la eliminacin), La
misma operacin, sin embargo, es muy eficaz en una lista doblemente ligada (lineal
o circular).
En esta seccin se presenta una representacin con rboles de una lista lineal en
la cual las operaciones p,ra encontrar el k-simo elemento de la lista y eliminar un
elemento especfico son un tanto eficaces. Mediante esta representacin, tambin se
puede construir una lista con elementos determinados. Tambin se considera en for-
ma brev.e la operacin de insertar un solo elemento nuevo.
Como se ilustra en la figura 5 .4.1, una lista puede representarse mediante un
rbol binario. La figura 5.4. la muestra una lista en elforma.to ligado regular, (h)

mientras que las figuras 5.4.lb y c muestran dos rboles binarios que representan
dicha lista. Los elementos de la lista original se representan por medio de hojas del
rbol (las que aparecen en la figura como cuadrados), mientras que los nodos no
hojas del rbol (que aparecen en la figura como crculos) estn presentes como par-
te de la estru_ctura interna del rbol. Asociado a cada nodo hoja estn los contenidos
del correspondiente elemento de la lista. Asociado a cada nodo no hoja hay un con-
tador que representa el nmero de hojas del subrbol izquierdo del nodo. (Aunque
este conteo se puede calcular de la estructura del rbol, se guarda como un elemento
de datos para no tener que volver a calcular su valor cada vez que se necesita). Los
elementos de la lista en su secuencia original estn asignados a las hojas del rbol en
la secuencia de en orden de las hojas. Advierta que en la figura 5 .4.1 varios rbole.s
binarios pueden representar la misma. lista.

Hallazgo del elemento k-simo

Para justificar el uso de tantos nodos extra para representar una lista, se pre-
senta un algoritmo para encontrar el .elemento k-simo de una lista representada rci:
mediante un rbol. Sea tree un apuntador a la raz del rbol y /count(p) el conteo
asociado al nodo no hoja apuntado por p[lcount/p) es el nmero de hojas del rbol
que tiene raz en node(left(p))]. El siguiente algoritmo emplea la variablefind para Figura 5.4.1 Una lista y dos rboles binarios correspondientes.
apuntar a ]a hoja que contiene el elemento k-simo de la lista.

288 Estructuras de datos en C Arboles 289


El algoritmo mantiene una variable r que contiene el nmero de elementos de
la lista que falta contar. Al principio del algoritmo, r se inicializa como k. En cada.
nodo no hoja node(p), el algoritmo determina desde los valores de r y lcount(p) si el
elemento k-simo se localiza en el subrbol derecho o izquierdo. Si la hoja est en el 3 2
subrbol izquierdo, el algoritmo procede directamente hacia ese subrbol. Si la hoja
deseada est en el subrbol derecho, el algoritmo procede hacia dicho subrbol des-
pus de reducir el valor de r por el valor de lcount(p). Se supone que k es menor o
igual que el nmero de elementos de la lista.

r = k;
p = tree;
wbile p no es un nodo hoja) r = \
i:t (r <= lcount(p))
p = le:tt(p);
else {
r -= lcount(p); (a)
p = right(p);
I* findeil *I
find = p;

La figura 5.4.2a ilustra el hallazgo del quinto elemento de una lista en el rbol
de la figura 5.4.1 b, y la 5.4.2b ilustra el hallazgo del octavo elemento en el rbol de
la figura 5.4. lc. La lnea punteada representa el camino que toma el algoritmo que
desciende por el rbol hasta la hoja apropiada. El valor de r (el nmero de elementos
que an debe contarse) se indica al lado de cada nodo encontrado por el algoritmo. 2
El nmero de nodos del rbol examinado para encontrar el elemento k-simo
de la lista es menor o igual que 1 ms la profundidad del rbol (el camino ms largo
en el rbol, de la raiz a una hoja). As, se examinan cuatro nodos de la figura 5.4.2a,
para encontrar el quinto elemento de la lista, y de la figura 5.4.2b, para encontrar el
octavo elemento de la lista. Si a una lista se representa como una estructura liga-
da, se accesan cuatro nodos para encontrar el quinto elemento de la lista [o sea, la
operacin p = next(p) se ejecuta cuatro veces] y se accesan siete nodos para
encontrar el octavo elemento.
Aunque esto no es un ahorro muy impresionante, considrese una lista con
1000 elementos. Un rbol binario de profundidad 10 basta para representar tal lista,
ya que log 2 l000 es menor que 10. As, encontrar el elemento k-simo (sin importar si
'b)
k fue 3, 253, 708 o 999) mediante tal rbol binario, requerira del examen de no ms
de 11 nodos. Puesto que el nmero de hojas de un rbol binario crece como 2d, don- Figura 5.4.2 Hallazo d~l n-simo elemento de una lista representada por medio de un rbol.
de des la profundidad del rbol, tal rbol representa una estructura de datos relati- .
vamente eficaz para el hallazgo del elemento k-simo de una lista. Cuando se usa un
rbol binario cuasi-completo, el elemento k-simo de una lista den elementos puede apuntador izquierdo derecho en el padre de la hoja eliminada di. Sin embargo, pa-
encontrarse en log 2n + l accesos de nodos como mximo, mientras que si se usara ra habilitar los accesos subsecuentes, hay que modificar los conteos en todos losan-
una lista lineal ligada se requeriran k accesos. cestros de di. La modificacin consiste en reducir lcount en 1 en cada nodo nd del
que di fue un descendiente izquierdo, ya que el nmero de hojas del subrbol iz-
Eliminacin de un elemento
quierdo de nd es uno menos. Al mismo tiempo, si el hermano de di es una hoja, se
Cmo puede eliminarse un elemento de una lista representada por un rbol? puede trasladar hacia arriba del rbol para tomar el lugar de su padre. Por lo tanto,
La eliminacin misma es relativamente fcil. Implica slo la asignacin de null un
Arboles 291
290 Estructuras de datos en C
se puede mover ese nodo hacia arriba, an ms lejos, si no tiene hermano en su;: left(f) = null;
nueva posicin. Esto puede reducir la profundidad del rbol resultante y hacer que right(f) = null;
los accesos siguientes sean un poco ms eficaces. lcount(f) = O;
Se puede, por lo tanto, presentar un algoritmo para eliminar una hoja de un< free node( q);
/* fin de if */
rbol apuntada por p (y en consecuencia un elemento de una lista) como sigue. (Los
nmeros de lnea a la izquierda son para referencias posteriores.)
q= f;
I* findewhile */
50 /* findeelse */
1 i f (p == tree) {
2 tree = null;
3 free node( p); La figura 5.4.3 ilustra los resultados de este algoritmo para un rbol en el cual
,; } los nodos C, D y B se eliminan en ese orden. Hay que asegurarse que se sigue la ac-
5 else { cin del algoritmo en esos ejemplos Obsrvese que, por consistencia, el algoritmo
6 f = father(p); mantiene un conteo O en los nodos hoja, aunque no se requiere el conteo para tales
7 /* eliminar node(p) y hacer que b seale a su hermano nodos. Advirtase tambin que el algoritmo nunc mueve un nodo que no es hoja
6 i f (p == left( f)) ( hacia arriba, aun cuando esto se podra hacer. (Por ejemplo, el padre de A yB en la
9 left(f) = null; figura 5.4.3b no ha sido movido hacia arriba). Aunque se puede modificar con faci-
10 b right( f); lidad el algoritmo para hacerlo (la modificacin se le deja al lector),' se ha evitado
11 --lcount( f);
por razones que sern evidentes en breve.
12 } Este algoritmo de eliminacin implica la inspeccin de dos nodos hacia arriba
13 else (
right(f) = null;
(el aicestro del nodo que se est eliminando y el hermano de ese ancestro) en cada
M
15 b = le.ft( f); nivel. As, la operacin que elimina el k-sirno elemento de una lista representada
16 } /* findeif *I mediante un rbol (la que implica encontrar el elemento y luego eliminarlo) requiere
~. 7 ii (node(b) es una hoja) { de.un nmero de accesos de nodos m.s o meios igual.a tres veces la profundidad del
16 /* traslade el contenido de (node(b) a su padre *I .rboL Aunque la eliminacin en una lista ligada exige accesar slo tres nodos (el no-
19 /* y libere node(b) *I do que precede y el que sigue al nodo que se va a eliminar, as como el propio nodo
20 info( f)= info( b); eliminado), la eliminacin del k,simo elemento requiere de un total de k + 2 accesos
21 left(f) = null; (k - l de los cuales son para localizar el nodo que precede al k-simo). Para listas
22 right(f) = null; largas, en consecuencia, la representacin mediante un rbol es ms eficaz.
23 lcount(f) = o;
De manera anloga, se puede comparar en forma favorable la eficacia de las
2,; free node(b);
listas representadas mediante rboles con las listas representadas mediante arreglos. Si
25 } findeil *I
/*
26 free node(p); una lista de n elem.entos se guarda en los primeros n elementos de un arreglo, en-
27 / *, ascienda por el rbol *I contrar el k-simo elemento requiere un solo acceso, pero eliminarlo exige el despla-
26 q = f; zamiento de los n-k elementos que seguan al elemento eliminado. Si se permiten
29 while (q != tree) ( espacios vacos en el arreglo, de manera que la eliminacin se pueda implantar con efi-
30 f = father( q); tacia (colocando un indicador en la posicin del arreglo que ocupa el elemento eli-
31 i f (q left(f)) minado.,. en lugar de recorrer todos los elementos que le shceden), el hallazgo del
32 /* la hoja eliminada fue un descendiente *I elemento k-simo requiere como mnimo de k accesos en el arreglo. La razn es que
/ izquierdo de node() *I ya no es posible conocer la posicin en el arreglo del k-sirrto elemento de la lista, ya
33 --lcount( f); que pueden existir espacios entre h; elementos del arreglo. (Debe observarse, sin
3,; b = right( f);
embargo, que si el orden de los elementos de la ista es irrelevante, el elemento
35
else
k-simo de un arreglo puede eliminarse eficazmente remplazndolo por el elemento
36
37 b=left(f); en la posicin n [el ltimo elemento] y ajustando el conteo a n - l. Sin embargo, es
36 I* node(b) es el hermano de node(q) *I improbable que se desee eliminar el k-simo elemento de una lista en la que el orden
39 if (b = = null && node(q) es una hoja) sea irrelevante, pues entonces ya no tendra importancia el k-simo elemento sobre
,;o /* traslade a su padre los contenidos */ los dems.)
q I* de node(q) y librelo */ Insertar un nuevo k-simo elemento dentro. de una lista representada por un
,;2 info(f) = info(q); rbol [entre el (k - 1) y el k-simo previo] es tambin una operacin relativamente

292 Estructuras de datos en C Arboles 293


3 eficaz. La insercin consiste en localizar el k-simo elemento, remplazarlo por un
nuevo nodo que no es hoja que tenga una hoja con el nuevo elemento como hijo
'. izquierdo y una hoja que contenga al antiguo k-simo elemento como hijo derecho y
ajustar conteos apropiados entre sus ancestros. Al lector se le dejan los detalles. (Sin
embargo, agregar de manera repetida un nuevo k-simo elemento con este mtodo
provoca que el rbol quede muy desequilibrado, ya que la rama que contiene el ele-

=> . mento k-simo se vuelve larga y desproporcionada en relacin a las de.ms. Esto sig-
nifica que la eficacia del hallazgo del elemento k-simo no es tan fabuloso como lo
sera en un rbol balanceado, en el que todos los caminos tienen ms o menos la mis-
ma longitud. Se invita al lector a encontrar una estrategia de "balanceo" para ali-
viar este problema. A pesar de este problema, si se hacen las inserciones en el rbol
de manera aleatoria, de tal manera que sea igualmente probable insertar un elemen-
io en cualquier posicin dada, el rbol resultante permanece por lo regular balancea-
(a)
do
,',:
y el hallazgo del elemento. k-simo sigue siendo eficaz.)

Implantacin de listas representadas por medio de rboles en C

Las implantaciones de los algoritmos de bsqueda y eliminacin en C son di-


rectas por medio de la representacin ligada de rboles binarios. Sin embargo, una
representacin de este tipo requiere de campos in/o, lcount,father, left y right para

=> cada nodo del rbol, mientras que un nodo de una lista slo necesita campos in/o y
next. Aunado al hecho de que la representacin por medio de un rbol requiere de casi
dos veces ms nodos que larepresentacin por medio de una lista ligada, este re-
querimiento de espacio puede hacer que la representacin por medio de un rbol sea
poco prctica. Se podra, por supuesto, utilizar nodos externos que contengan slo
un campo in/o (y quizs un campo father) para las hojas y nodos internos que con-
(b)
tengan campos lcount,father, left y right para los nodos no hojas. Aqu no se consi-
dera esta posibilidad.
En la representacin secuencial de un rbol binario, los requerimientos de es-
pacio estn lejos de ser tan fabulosos. Si se asume que no se necesitan inserciones
una vez que se construye el rbol y que se conoce el tamao inicial de la lista, se
puede apartar un arreglo para guardar la representacin de la lista como un rbol
estrictamente binario cuasi-completo. En esa representacin los camposfather, left
y right son innecesarios. Como se mostrar ms adelante, siempre es posible cons-

=> tru.ir una representacin de una lista por medio de un rbol binario cuasi-completo;
Ya que se construy el rbol, los nicos campos requeridos son in/o, lcounty
un campo que indique cuando un elemento del arreglo representa un nodo existente
o uno eliminado. Tambin, como ya se observ antes, lcount slo es necesario para
nodos del rbol que no sean hojas, por lo que podra usarse una estructura con el
campo lcount o el campo in/o, dependiendo de si el nodo es o no una hoja. Se deja
esa posibilidad como ejercicio al lector. Tambin es posible eliminar la necesidad del
( e)
campo used con un cierto costo de eficacia en tiempo (ver ejercicios 5.4.4 y 5.4.5). Se
asumen las siguientes definiciones y declaraciones (suponer 100 elementos en la
Figura 5.4.3 El algoritmo de eliminacin. lista):

294 Estructuras de datos en C


Arboles 295
#define MAXELTS 1001 OO /* Nmero mximo de elementos en lista aelete(p)
#define NUMNODES 2*MAXELTS - 1 int p;
/ *
11
#define BLANKS 100 20 blanc {
struct nodetype { int br f, q;
char info [ 20 l ;.
int lcount; if ( p == o)
int used; node[p]. used = FALSE; LSE ; I* Lneas 1~4 del algoritmo *!
node[NUMNODES]; else {
f = ( p-1) / 2; I * Lnea 6 del algoritmO */
if(p%2!=0j I * Lnea 8 del algoritmo */
Un nodo no hoja se puede reconocer mediante un valor in/o igual a BLANKS. b = 2*f + 2;
father(p), !eft(p} y right(p) pueden implantarse de manera regular cmo (p - 1)/2/ --node[fJ.lcount;
2 * p + 1 y 2 * p + 2; respectivamente.
A continuacin se presenta narutina en C par encontrar el k-simo elemen 1 else
to. Usar la rutina de biblioteca strmp, la que da como resultado' O si dos cadenas b ="'2*f + 1;
son iguales. if (sttcmp(node[b] .info, BLANKS) != O) {
l * Lneas 17-25 del algoritmo *I
strcpy(node[fl,~nfo, node[bJ.infO);
findelement(k) node[bJ.used = PALSE;
int k; I* fin deH *I
{
nde[pJ.used = FALSE; !*" Linea 26 del algoritmo *I
q = f; I* Lnea 28 del algoritmo *I
r = k; while (q != O) {
p = O; f = ( q~1) / 2; I* Lnea 30 del algoritmo *!
while (strcmp(node[pJ.info, BLANKS) O) if(q%2!-D) I* Lnea 31 del algoritmo *!
if (r <= node[p].lcount) --node[fJ.lcount;
p = p*2 + 1; b = 2*f + 2; 1
else { :o

r ~~ _riodeCpi.i~ou~t; else
p = p*2 + 2; .
b = 2*f + 1;
ij
} /* fin deif *.! l1
if (1node[bl.used && strcmp(node[q].info, BLANKS)
return(p); ti
!* Hn de findeletiient ' */ ! = O) { / *
Lineas 39-47 del algoritmo *I .,
strcpy(node[fl.info, node[ql.info); \,1

node[ql.used = FALSE; ::
La rutina en C para eliminar la hoja apuntada por p mediante la representa, I* fin dei[ *! )j

cin secuencial es un poco ms sencilla que el algoritmo correspondiente presentado q = f; ,,"'


con anterioridad. Se pueden ignorar.todas las asignaciones de nu/1 (lneas 2, 9, 14; I * fin. de while *I ;
21, 22, 43 y 44) debido a que no se usan apuntadores. Tambin se pueden ignorar las I* findeH *I
I* fin dedelete *I
il,
asignaciones de O a un campo lcount (lneas.23 y' 45), pes dicha asignacin es parte
de la conversin de un nodo no hoja a hoja y en esta representacin en C no se usa el
campo /court para nodos hojas, Un nodo puede reconocerse como hoja-(lineas 17 y "
39) por un. blanco. como valor in/o, y. el. apuntador b como nu/1 (lnea 39) por un Nuestro uso de la representacin secuencial explica la razn paranc:i trasladar un
valor.FALSE para node[b].used; La liberacin de un nodo (lineas 3; 26 y 46) se nodo ~? hoja sin herm_ano ms arriba del rbol durante la eliminacin. En la repre-
realiza haciendo suJcampo. used-igual a FALSE.'La rutina utiliza la rutina de sen_tac1on secuencial, dicho proceso de traslacin hacia arriba implica copiar los con-
biblioteca strcpy(s,t), la que.asigna la cadena ta la c.adena s y la rutina strcmp para terndos de t~do~ los nodo~ al subrbol dentro del arreglo, en tanto que si se usa la
verificar si dos cadenas son iguales. representac1on hgada 1mphca la modificacin de un solo apuntador.

296 Estructuras de datos en C


297
Construccin de una lista representada mediante un rbol fer (i=D; i < n-1; i++)
node[iJ.used = TRUE;
Ahora se toma otra vez el postulado de que, dada una lista den elementos, es . node(iJ.lcount.= D;
strcpy(node[iJ.info, BLANKS);
posible construir un rbol estrictamente binario cuasi-completo que represente la lis-
} !* fin defor *I
ta. Ya se observ en la seccin 5.1 que es posible construir un rbol estrictamente /* colocar valores en los campos lcount "'/
binario cuasi-completo con n hojas y 2 * n- 1 nodos. Las hojas de un rbol de este for (i=n-1; i < size; i++) {
tipo ocupan los nodos numerados de n - I a 2 * n - 2. Si des el entero ms pe. I* seguir la ruta de cada hoja a la raz *I
queo, de manera que 2d sea mayor o igual que n (esto es, si des igual al entero ms p = i;
pequeo que es mayor o igual que log 2n), des igual a la profundidad del rbol. El while (p != O) {
nmero asignado al primer nodo en el nivel del fondo del rbol es 2d- l. A los pri- f = ( p-1) / 2 ;
meros elementos de la lista se les asignan los nodos del 2 d - 1 al 2 * n - 2 y al resto if (p % 2 != O)
(si lo hay), los nodos numerados den - 1 a 2d- 2. En la consrruccin de un rbol ++node[fJ.lcount;
que representa una lista con n elementos, se pueden asignar elementos a los campos p = f;
nfo de las hojas del rbol en esa secuencia y asignar una cadena de blancos a los I * fin de wbile * I
!*. fin delor *I
campos info de nodos no hojas, numerados d.e. O a n - 2. Tambin es muy simple
inicializar el campo used como true en todos los nodos numerados de Oa 2 * n - 2.
! * fin de buildtree * /
Inicializar los valores del arreglo lcount es ms difcil. Se pueden usar dos m-
todos: uno que implica ms tiempo y otro que implica ms espacio. En el primer El segundo mtodo usa un campo adicional, rcount, en cada nodo para guar-
mtodo se inicializan con Otodos los campos !count. Luego, se asciende por el rbol, dar el nmero de hojas en el subrbol derecho de cada nodo no hoja. Este campo,
por turnos, de cada hoja a la raz. Cada vez que se alcanza un nodo desde su hijo iz- as como el campo fcount, se hace igual a 1 en cada nodo no hoja que sea padre de
quierdo, se agrega 1 a su campo lcount. Despus de ejecutar este proceso para cada dos hojas. Sin es impar, de tal manera que hay un nodo [numerado (n - 3)/2] que
hoja, todos los valores de lcount quedan asignados apropiadamente. La siguiente es el padre de una hoja y de una no hoja, se asigna 2 a lcount en ese nodo y I a
rutina usa este mtodo para construir un rbol de una lista de datos de entrada: rcount.
El algoritmo avanza luego sobre los elementos restantes del arreglo en orden
buildtree ( n) inverso, asignando a !count en cada nodo a la suma de lcount y rcount en el hijo
int n; izquierdo del nodo y a rcount la suma de lcount y rcount en el hijo derecho del nodo.
{ Se deja al lector la implantacin en C de esta'ttnica. Obsrvese que rcount puede
int d, f, i, p, power, size; implantarse como un arreglo local en bu!dtre y no como un campo en cada nodo,
ya que sus valores dejan de usarse una vez que se termina de construir el rbol.
!* calcular la profundidad del rbol d y el valor de 2d *! Este segundo mtodo tiene la ventaja de visitar una vez cada nodo no hoja pa-
d' = o;
ra calcular de manera directa su valor lcount (y rcount). El primer mtodo visita
power = 1;
cada nodo que no es hoja una vez para cada una de sus hojas descendientes, agregando
while (power < n) {
+ :-d; l a /count cada vez que se encuentra que la hoja es un descendiente izquierdo. Para
power *= 2; compensar esta ventaja, el segundo mtodo requiere un campo extra rcount,
fin de while *I
I* mientras que el primero no requiere campos extra.
I* asignar los elementos de la lista, inicializar las etiquetas *I
! * empleadas e inicializar el campo lcount con *I Revisin del problema de Josephus
I* O en todos los nodos que no son hojas *I
size = 2*n - 1; El problema de Josephus de la seccin 4.5 es un ejemplo perfecto de la utilidad
fer (i = power-1; i < size; i++) de la representacin de una lista mediante un rbol binario. En ese problema fue ne-
scanf( 11 %d 11 , &node(i].info);
cesario encontrar en forma repetida el m-simo elemento siguiente de una lista y
node[iJ.used = TRUE;
luego eliminarlo. Esas son operaciones que se pueden ejecutar con eficacia en listas
I* fin defor *I
for (i=n-1; i < power-1; i++){ representadas por rboles.
scanf( 11 %s 11 , node[iJ.info); Si sze es igual al nmero de elementos presentes en una lista, la posicin del
node[il.used = TRUE; m-simo nodo siguiente al nodo que se acaba de eliminar en la posicin k se da por 1
I * fin de or * I + (k- 2 + m) % sze. (Aqu se asume que el primer nodo de la lista est en la posi-

298 Estructuras de datos en C Colas y listas 299


cin 1, no en la 0.) Por ejemplo, a una lista con cinco elementos se le elimina el terce- I* fin de for *I
ro y se desea encontrar el cuarto despus del elemento eliminado, size = 4, k = 3 y printf(''%d 1' , node[O].info.);
m = 4. Entonces k - 2 + m es igual a 5 y (k - 2 + m) % size es 1, por lo que el /* fin de main *
cuarto elemento, a partir del elemento eliminado, est en la posicin 2. (Despus de
eliminar el elemento 3, se cuentan los elementos 4, 5, 1 y 2). Se puede por lo tanto,
escribir una funcin en C follower para encontrar el in-simo nodo que sigue a un
nodo en la posicin k que acaba de ser eliminado y asignar a k su posicin. La rutina
llama a la rutinafindelement presentada con anterioridad. 5.4,t. Pruebe que un rbol estrictamente binario cuasi-completo se asigna el nmero 2n al
nodo de la extrema izquierda en el nivel n.
follower(size, m, pk) 5.4,2. Pruebe que la extensin (ver ejercicio 5.3.5) de un rbol binario cuasi-completo es
int size, m, *pk; cuasi-completo.
1
5.4,3, Para qu valores de n y m es ms rpida de ejecutar la sOlucin al problema de
' int j, d; Josephus proporcionada en esta seccin ~ue la de la seccin 4.5? Por qu?
5.4,4. Explique cmo se pu~de eliminar la necesidad del campo used si se opta por no mover
j k-2+m; hacia arriba una hoja recin creada que no tenga hermano durante la eliminacin.
*pk = (j % size) + 1;
5.4,5, Explique cmo elimi~ar la necesidad del campo used si se hace li:ount igual a -1 en un
return(findelement(*pk));
nodo no hoja que se convierte a hoja y coloca blancos en in/o en un nodo eliminado.
I * fin de follOwer *I
5.4.6. Escriba en C la rutina buildtree, en la ual cada nodo sea visitado una sola vez median-
. te un arreglo rcount como el descrito en el fexto.
El siguiente programa en C implanta el algoritmo de Josephus mediante un r- 5.4.7. Muestre cmo representar una lista ligada como un rbol binario cuasi-completo en el
bol como representacin de lista. El programa recibe un nmero de personas en el que .cada elemento de la lista est representado por un nodo del rbol. Escribir una fun-
crculo (n), un enter count (m) y los nombres de las pers.onas del crculo en orden, cin eii C que seale con un apuntador ".l_lemento k-simo de una lista de este tipo.
empezando con la persona a partir de la cual comienza el conteo. Las personas del
crculo se cuentan en orden y la persona en la que se alcanza el conteo de entrada
abandona el crculo. Luego se inkia el conteo otra vez a partir de 1,' comenzando "
con la siguiente persona. El programa imprime el orden en eique las personas aban- ARBLES Y SUS APLICACIONES
donan el crculo. En la seccin 4.5 se present un progrma para .hacer esto por
medio de una lista circular en la. que se accesan (n - 1) * ,;, .nodos na vez que se En esta seccin se examinan los rboles generales y sus representaciones. Tambin se
construye la lista inicial. El siguiente algoritmo accesa menos nodos que (n - 1) * investigan algunos de sus usos en la solucin de problemas.
1og 2 n ya que se construy el rboL Un rbol es un conjunto finito no vaco de elementos, en el que a uno de los
elementos se le llama raz y los dems estn particionados en m > = Osubconjuntos
I* . aqu se colocan las definiciones de *I disjuntos, cada uno de los cuales es un rbol en s mismo. Cada elemento de un rbol
I* MAXELTS, NUMNODES, BLANKS y nodeptr *I se llama nodo del rbol.
main () La figura 5.5. l ilustra algunos rboles. Cada nodo puede ser la raz de un rbol
con Oo ms subrboles. Un nodo que no tenga subrboles es una hoja. Los trminos
int k, m, n, p, size; padre, hijo, hermano, ancestro, descendiente, nivel y profundidad se usan en el mis-
struct nodetype node[NUMNODES]; mo sentido que para rboles binarios. Tambin se definen tanto el grado de un nodo
en un rbol como el nmero de sus hijos. As, en la figura 5.5.la, el nodo C tiene
scanf( 11 %d%d 11 , &n, &m);
grado O (y es por lo tanto una hoja); el nodo D tiene grado l; .el B, 2, y el A, 3. No
buildtree(n);
k = n + 1 ; / * de inido hemos Helimnado" a la (n + l)~sima hay lmite superior sobre el grado de un nodo,
*I
I* persona *I Comprense los rboles de las figuras 5.5.la y c. Ambos son equivalentes co-
for ( size = n; sizec > 2; --size) { mo rboles. Cada uno tiene a A como raz Ytres subrboles. Uno de ellos tiene raz
I * repetir hasta que slo quede una persona * I C sin subrboles, otro tiene raz D con unsolo subrbol enraizado en G y el tercero
p = follower(size, m, &k); tiene raz en B con dos subrboles enraizados en E y F. La nica diferencia entre las
printf( 11 %d\n 11 , node[pJ.info); dos ilustraciones es el orden en que estn dispuestos los subrboles. La definicin
delete(p); de un rbol no hace distincin entre los subrboles de un rbol general, a diferencia de

300 Estructuras de datos en C Arboles 301


rbol ordenado se le llama con frecuencia el hijo mayor del nodo, y al ltimo hijo, el
un rbol binario en el que se hace distincin entre sus subrboles izquierdo
derecho. y hijo menor. Aunque los rboles de la figura 5.5. la y c son equivalentes como rboles
no. ordenados, ambos son diferentes como rboles ordenados. En el resto de este
Un rbol ordenado se define como un rbol en el cual los subrboles de cad
captulo se usa la palabra "rbol" para referirse a un "rbol ordenado". Un bosque
nodo forman un :~junt~.ordenado. En un rbol ordenado se puede hablar del pri~
es un conjunto ordenado de rboles ordenados.
mero, segundo o ultimo h1Jo de un nodo particular. Al primer hijo de un nodo de un
Surge la pregunta de si un rbol binario es un rbol. Todo rbol binario, ex-
. cepto el vaco, es de hecho un rbol. Sin embargo no todo rbol es binario. Un nodo
de un rbol puede tener ms de dos hijos y un nodo de un rbol binario no. Incluso
un rbol cuyos nodos tengan como mximo dos hijos no es por fuerza un rbol bina-
rio. Esto es porque un solo hijo de un rbo.l general no se designa como hijo "iz-
quierdo" o "derecho", mientras que en un rbol binario todo hijo tiene que ser un
hijo. "izquierdo" o "derecho": En realidad, aunque un rbol binario no vaco es un
rbol, las designaciones de izquierdo y derecho no tienen significado dentro del con-
texto de un rbol (excepto quizs para ordenar los dos subrboles de aquellos nodos
con dos hijos). Un rbol binario no vaco es un rbol en donde cada uno de sus
nodos tiene como mximo dos subrboles que tienen la designacin agregada de
"izquierdo" o "derecho".

Representaciones de rboles en C

Cmo puede representarse en C un rbol ordenado? De inmediato acuden a


la mente dos posibilidades: se puede declarar un arreglo de nodos del rbol o se
puede asignar una variable dinmica para cada nodo creado. Sin embargo, cul
debe ser la estructura de cada nodo individual? En la representacin de un rbol bi-
nario, cada nodo contiene un campo de informacin y dos apuntadores a sus dos hi-
jos. Pero, cuntos apuntadores debe contener el nodo de un rbol? El nmero de
hijos de un nodo es variable y puede ser tan grande o pequeo como se desee. Si se
declara de manera arbitraria

#define MAXSONS 20

struct treenode
int info;
struct treenode * padre;
struct treenode * hijos[MASXONS]; )
}; I
Ji,,
1~
se restringe entonces el nmero de hijos de un nodo a un mximo de 20. Aunque en 'li,1
muchos casos basta esto, a veces es necesario crear un nodo de manera dinmica con
21 o 100 hijos. Peor que esta posibilidad remota es el hecho de que se reserven veinte <\i

unidades de memoria aun cuando un nodo pueda tener en realidad 1 o 2 (o incluso O)


hijos. Esto es un tremendo despilfarro de espacio.
t: Otra posibilidad es ligar todos los hijos de un nodo en una lista lineal. As, el
conjunto de nodos disponibles (mediante la implantacin con arreglo) puede decla-
(e) rarse como sigue:

Figura 5.5.1 Ejemplos de rboles-.

Arboles 303
302 Estructuras de datos en C
son info next
#define MAXNODES 500

struct treenode
int info;
int father;
int son;
int next;
};
struct treehode node[MAXNODES];
n n n n
u u u u
node[p].son apunta al hijo mayor de node[p], 'y node[p].next apunta al sil 1 F 1 1 G
I
guiente hermano ms joven de node[p]. 1 I 1 1

De manera alternativa, un nodo puede declararse como variable dinmica:


(>)

struct treenode {
int 'info;
struct tceenode *father;
struct treenode *son;
struct treenode *next;
};
typedef struct treenode *NODEPTR;

n n
Si todos los recorridos son de un nodo a sus hijos, puede omitirse el campo father. u u
La figra 5.5.2 illlstra las representaciones de los rboles de la figma5.5.1 en estos 1 L 1
1 1
mtodos cuando no se necesita el campo.father.
Incluso cuando es necesario accesr el padre de un nodo, pllede omitirse el
n n
campofather colocando un apuntador al padre en el campo next del hijo ms joven, u u
1 o 1
en lugar de dejarlo como nu/1. Puede usarse entonces un campo lgico adicional pa' 1 1
ra indicar si el campo next apunta a un hijo "real" o al padre. De manera alternativa
(en la1mplantacin con arreglo de nodos), los contenidos del campo next pueden te- n n
ner indices tanto negativos como positivos. Un valor negativo indicara que el cam- u p
u
1 1
po next apunta al padre del nodo y no a su hermano, y el valor absoluto del campo 1 1
next produce el apuntador real. Esto es similar a la representacin de hebras en r- (b)
boles binarios. Por supuesto, en cada uno de estos dos ltimos mtodos, se requerir

w
recorrer la lista de los hijos hacia el rn.s joven; en un nodo dado, para accesar al
padre del nodo.
Si se considera que son est en correspondencia con el apuntador !eft de un
nodo de un rbol binario y que next est en correspondencia con su apuntador right,
este mtodo representa en realidad un rbol ordenado general mediante un rbol
binario. Se puede dibujar este rbol binario corno el rbol original con una inclina-
cin de45 grados y con todas las ligas padre-hijo eliminadas, excepto aquellas entre
un nodo y su hijo mayor y con las ligas agregadas entre cada nodo y su hermano me'
ffiQ]-Q] ,, ,

. wF. .
n n n n
nor prximo. La figura 5.5.3 ilustra los rboles binarios que corresponden a los u u , u
G 1 1 . 1 E 1
rboles de la figura 5.5.1. 1
1 1 I 1 1
En realidad, un rbol binario puede usarse para representar un bosque entero,
pues el apuntador next de la raz de un rbol puede usarse para apuntar al siguiente r- (o)

bol del bosque. La figura 5.5.4 ilustra un bosque y su rbol binario correspondiente.
Figura s.s:2 Rpres'entaciones. de rboles.

304 Estructuras de datos en C Arboles 305


Recorridos de rboles

Los mtodos de recorrido para rboles binarios inducen mtodos de recorrido


para bosques. Los recorridos en preorden, en orden y postorden de un bosque
pueden definirse como los recorridos en preorden, en orden y postorden de su rbol
binario correspondiente. Cuando un bosque est representado como un conjunto de
nodos de variable dinmica, con apuntadores next y son igual que antes, se puede
escribir una rutina en C para imprimir los contenidos de sus nodos en orden como
sigue:

intrav(p)
NODEPTR p;
{
if (p != NULL) {
intrav(p->son);
printf ( 11 %d \n 11 , p->info); (a)

intrav(p->next);
!* indeif *I
I * fin de intrav * I

Las rutinas para los recorridos en postorden y preorden son similares.


Estos recorridos de un bosque tambin pueden definirse en forma directa de la
siguient_e manera:

PREORDEN l. Visitar la raz del primer rbol del bosque.


K
2. Recorrer en preorden el bosque formado por subr-
boles del primer rbol, en caso de que lo haya.
3. Recorrer en. preorden el bosque formado por los
rboles restantes del bosque, en caso de que los
haya. (b)

EN ORDEN l. Recorrer en orden el bosque formado por los subr-


boles del primer rbol del bosque, en caso de que lo
haya.
2. Visitar la raz del primer rbol.
3. Recorrer en orden el bosque formado por los rbo-
les restantes del bosque, en caso de que los haya.

POSTOR DEN l. Recorrer en postorden el bosque formado por los


subrboles del primer rbol del bosque, en caso de
que los haya.
2. Recorrer en postorden el bosque formado por los (e)
rboles restantes del bosque, en caso de que los
haya. Figura 5.5.3 Arboles binarios que corresponden los rboles de la figura 5.5.1.
3. Visitar la raz. del primer rbol del bosque.

306 Estructuras de datos en C Arboles 307


Los nodos del bosque de la figura 5.5.4a pueden agruparse en preorden como
A ABCDEFGHIJKLMPRQNO, en orden como BDEFCAJJKHGRPQMNOL y
en postorden como FEDCBKJIHRQPONMLGA. Denomnese a un recorrido de un
rbol binario como recorrido binario y a un recorrido de un rbol general ordenado
como recorrido general.

Expresiones generales como rboles


D
Un rbol ordenado puede usarse para. representar una expresin general casi
del mismo modo en que se usa un rbol binario para representar una expresin bina-
ria. Como un nodo puede tener cualquier nmero de hijos, los nodos no hoja no
necesitan representar exclusivamente operadores binarios, sino que pueden representar
operadores con cualquier nmero de operandos. La figura 5.5.5 ilustra dos expre-
siones y sus representaciones por medio de rboles. El smbolo % se usa para repre-
sentar la negacin unaria y as evitar confusin con la sustraccin binaria que se
representa con un signo menos. Una referencia a una funcin del tipo f(g,h,iJ)
est considerada como el operador f aplicado a los operandos g,h,i y j.
Un recorrido general de los rboles de.la figura 5.5.5, en preorden da como
resultado las cadenas*% + AB - + Clog + D ! E FGHJJy q + AB sin C* X
+ Y Z, respectivamente. Estas son las versiones prefijas de las dos expresiones. As,
se observa que el recorrido general en preorden del rbol de una expresin produce
su versin prefija. El recorrido general en en orden da como resultado las cadenas
(a) . respectivas AB + % CDE ! + log + GHIJF - * y AB + C sin XYZ + q,
que son las versiones postfijas de las dos expresiones.
El hecho de que un recorrido general en en orden conduzca a una expresin
postfija puede parecer sorprendente a primera vista. Sin embargo, la razn de ello se
aclara cuando se examina la transformacin que ocurre al representar un rbol orde-
nado general por medio de un rbol binario. Considrese un rbol ordenado en el
que cada nodo tiene cero o dos hijos. Dicho rbol se ilustra en la figura 5.5.6a, y su
rbol binario equivalente, en la figura 5,5 ..6b. Recorrer el rbol binario de la figura .,
,
5.5.6b es lo mismo que recorrer el rbol ordenado de la figura 5.5.6a. Sin embargo, ;j
un rbol como el de la figura 5.5.6a puede considerarse como un rbol binario por
derecho propio y no como un rbol ordenado. As, es posible ejecutar un recorrido 'I
:1
binario (en lugar de un recorrido general) en forma directa en el rbol de la figura
5.5.6a. Bajo esa figura estn los recorridos binarios de ese rbol y arriba de la figura ,;'I
5.5.6b estn los recorridos binarios del rbol de esa figura, que son los mismos re- i,,
,,
corridos del rbol de la figura 5.5.6a si ste se considera como un rbol ordenado. ~
Obsrvese que lo.s recorridos en preorden de los dos rboles binarios son los
mismos. As, si un recorrido en preorden de un rbol binario que representa una
expresin binaria conduce a la forma prefija de la expresin, ese recorrido en un r-
bol ordenado que representa una expresin general que slo tiene operadores bina-
rios tambin da como resul.tado la versin prefija. Sin embargo, los recorridos en
(b) postorden de los dos rboles binarios no son los mismos. Ms bien, el recorrido
binario en orden del segundo (que es el mismo recorrido general en orden del primero
Figura 5.5.4 Un bosque y su 8rbol binario correspondiente. si ste se considera un rbol ordenado) es igual al recorrido binario en postorden del
primero. As, el recorrido general en orden de un rbol ordenado que representa una

Estructuras de datos en e
Arboles 309
expresin binaria es equivalente al recorrido binario en postorden del rbol binario
que representa a esa expresin, y produce la versin postfija.

Evaluacin del rbol de una expresin

Supngase que se desea evaluar una expresin cuyos operandos son todos
'constantes numricas. Tal expresin puede representarse en C con un rbol cada uno
de cuyos nodos se declara mediante:

#define OPERATOR o
#define OPERAND 1
struct treenode {
short int utype I* OPERATOR u OPERAND *I
union (
char operator[1DJ;
float vi11;
info;
struct treenode *son;
struct treenode *next;
};
typedef treenode *NODEPTR;

Como ya se ilustr antes, los apuntadores son y next se usan para ligar los nodos de
(a) -(A + 8) (C + /og(D + E!) -f(G, 11, /,J)) un rbol. Como un nodo contiene informacin que puede ser un nmero (operando)
o una cadena de caracteres (operador), la porcin de informacin del nodo es una
componente unin de la estructura.
Se desea escribir una funcin en C, evaltree(p), que acepte un apuntador a un
rbol de ese tipo y d como resultado el valor de la expresin representada por el r-
bol. La rutina eva!bintree presentada en la seccin 5.2 ejecuta una funcin similar
para expresiones binarias. eva!bintree utiliza una funcin oper que acepta un
smbolo de operador y dos operandos numricos y regresa el resultado numrico de
la aplicacin del operador a los dos operandos. Sin embargo, en el caso de una
expresin general no puede usarse una funcin de este tipo, ya que el nmero de ope-
randos (y por consiguiente el nmero de argumentos) vara con el operador. Por lo
tanto, se introduce una nueva funcin app!y(p), la que acepta un apuntador al rbol
de una expresin que contenga un solo operador y sus operandos numricos y regre-
sa el resultado de aplicar dicho operador a sus operandos. Por ejemplo, el resultado
de llamar a la funcin app!y(p), con parmetro p apuntando al rbol de la figura
5.5.7 es 24. Si la raz del rbol que se transfiere a evaltree representa un operador,
c.ada uno de sus subrboles se remplaza por nodos del rbol que representan los re-
sultados numricos de su evaluacin para que la funcin app!y pueda ser llamada.
(b) q(A + 8, sen(C), X, Y + Z)) Al evaluar la expresin, los nodos del rbol que representan operandos se liberan y
los nodos operador se convierten en nodos operando.
En seguida se presenta un procedimiento recursivo rep!ace que acepta un apun-
Figura 5.5.5 Represent<1cin por medio de un rbol de una expresin aritmtica.
tador al rbol de una expresin y remplaza el rbol por el nodo de un rbol que con-
tiene el resultado numrico de la evaluacin de dicha expresin.

310 Estructuras de datos en e 311


Arboles
hijo prximo etiqueta operador/val

nulo oprtr

hijo prximo etiqueta ~perador/val

nulo opnd 4

(a)

Preorden: + * AB + * CDt:
En orden: A * 8 + C * D + H hijo prximo etiqueta operador/val
Postorden: AB * CD * ,~- + +

nulo nulo opnd 6

Figura 5.5.7 Arbol de una expresin.

replace{p)
NODEPTR p;
{
float value;
NODEPTR q, r;

if {p->utype -~ OPERITOR) {
/* el rbol tiene un operador */
I* como su raz *I
Preorden: + * AB + * CD..'
En orden: AB * Cf)',i, /:' + + g = p->son;
Postorden: BA OC/:.*:+ * + while ( q NULL) {
I* reemplazar cada. uno de los .sizbrboles *I
!* con operandos *I
replace(q);
q = q->next;
I * fin de while * I
D
/* aplique el operador de la raz a los *I
/* operandos de los subrboles *I
value apply(p);
(b)
/ * reemplace el operador por el resultado
p->utype - OPERAND;
p->val = value;
/* liberar todos los subrboles *I
Figura 5.5.6
q = p->son;
p->son = NULL;

3.12 Estructuras de datos en C Arboles 313


while (q != NULL) !* fin de i1 *I
r = q if (p->son != NULL)
q = q->next; printf(insercin no vlida\ n 11 } ;

free ( r); exit(L);


I * fin de while *I !* fin de i *I
I* fin deif *I p->son = list;
/* fin de replace *I /* fin de setsons *I
La funcin eva!rree puede escribirse ahora como sigue: Otra operacin comn es addson(p,x), en la que p apunta al nodo de un rbol
y se desea agregar un nodo que contenga x como el hijo ms joven de node(p). La
float evaltree(p) rutina en C para implantar addson se presenta a continuacin. La rutina llama a la
NODEPTR p;
funcin auxiliar getnode, que asigna un nodo y regresa un apuntador al mismo.
(
NODEPTR q;
addson(p, x)
replace(p); NODEPTR p;
return(p->val); int x;
free(p); (
I* fin de evaltree */ NODEPTR q;

i f (p == NULL)
Despus de llamar a evailree(p), se destruye el rbol y el valor d.e p deja de te- printf("no se puede realizar la ins8rcin \n");
ner significado. Este es el caso de un apuntador indefinido, en el que una variable exit(1);
apuntador contiene la direccin de una variable liberada. Los programadores de C I* fin de if *I
que usan variables dinmicas deben ser cuidadosos para reconocer tales apuntadores * el apuntador q recorre la lista de hijo$ de P *I
y no usarlos posteriormente. * r est un nodo detrs de q *I
r = NULL;
Construccin de un rbol q = p->son;
while (q != NULL)
En la construccin de un rbol se usan con frecuencia varias operaciones. En- r = q;
seguida se presentan algunas de ellas y sus implantaciones en C. En la representacin q = q->next;
!* fin de whle- *I
en C, se asume que los apuntadores fa/her no se necesitan, por lo que no se usa el
campofather y el apuntador next del nodo ms joven es nu/1. Si ste no fuese el caso,
/* En este punto,- r seala al hijo. ms joven de p, */
/* o es nulo si P no tiene hijos *I
las rutinas seran un poco ms complejas y menos eficaces. q = getn.ode();
La primera operacin que se analiza es setsons. Esta operacin acepta un q->info = x;
apuntador al nodo de un rbol que no tenga hijos y una lista lineal de nodos ligados q->next ~ NULL;
a travs del campo next. setsons establece los nodos en la listacomo hijos del nodo i f (r == NULL) /* pnotienehijos */
del rbol. La rutina C para implantar esta operacin es directa (se usa la implanta- p->son = q;
cin de memoria dinmica): else
r->next =- q;
I * fin de addson * I
setsons(p, list)
NODEPTR p, list;
( Obsrvese que para agregar un nuevo hijo a un nodo, se tiene que recorrer la
!* p seala un nodo del rbol~ list *! lista de hijos existentes. Como agregar un hijo es una operacin comn, con fre-
I* a una lista de nodos ligados por medio *I cuencia se usa una representacin que realice esta operacin con .ms eficacia. En es-
!* de sus campos next *I ta representacin afternativa, la lista de hijos se ordena del menor al mayor Yno a la
i f ,(p == NULL) ( inversa. As, son(p) apunta al hijo menor de node(p) y next(p) a su siguiente herma-
printf("insercin no vlida \n"); no mayor que l. En esta representacin, la rutina addson,puede escribirse como
exit(L);
sigue:

314 Estructuras de datos en C Arboles 315


addson(p, x) por un carcter que representa al operador, y una funcin por el carcter 'F' seguido
NODEPTR p; por el nombre de la funcin.
int x; Considere las definiciones de expresin, trmino y factor determinadas al final de la
{ 5.5.9.
seccin 3.2. Asignada una cadena de letras, signos ms, asteriscos y parntesis que for-
NODEPTR q;
men una expresin vlida, se puede formar un rbol de sintaxis para la cadena. Tal
rbol se ilustra en la figura 5.5.8 para la cadena "(A + B) * (C + D)". Cada nodo de
if (p - - NULL) un rbol de ese tipo representa una subcadena y contiene una letra (E para denotar
prin t f C'insercin no vlida \ n"); expresin, Tpara trmino, Fpara factor o S para smbolo) y dos enteros. El primero es
exit(1);
la posicin dentro de la cadena de entrada donde comienza la subcadena representada
I* fin de f *I por ese nodo; el segundo es la longitud de la _subcadena. (La subcadena representada
q - getnode ( )';
por cada nodo se muestra debajo del nodo de la figura.) Las hojas son todas nodos S y
q->info = x; representan smbolos simples de la entrada original. La raz del rbol debe ser un nodo
q->next = p->son;, E. Los hijos de cualquier nodo N que no sea S representan las subcadenas que compo-
p->son = q;
nen el objeto gramatica representado por N.
I * fin de addson *I Escribir una rutina en C que acepte una cadena como Sta y construya un rbol de sin-
taxis para la misma.

EJERCICIOS
UN EJEMPLO: ARBOLES DE JUEGO
5.5.l. Cuntos rboles con n nodos existen?
5.5.2. Cuntos rboles con n nodos y nivel mximo m existen? Una aplicacin de los rboles se encuentra en los juegos y un participante es la com-
5.5.3. Pruebe que si se apartan en cada nodo de un rbol general m campos apuntadores para putadora. Esta aplicacin se ilustra escribiendo un programa en C para determinar
apuntar a un mximo de m hijos y si el nmero de hados del _rbol es n, el nmero de el "mejor" movimiento en el juego del "gato", a partir de una posicin determina-
campos apuntadores a hijos.que so.n nulos es n * (m .- 1). + l. da del tablero.
5.5.4._ Si se representa un bosque por medio de un rbol binario como en el texto, mostrar que Supngase que hay una funcin eva!uate que acepta una posicin del tablero y
el nmero de ligas derechas nulas es 1 ms que el nmero de nodos no hojas del bosque. una indicacin de un jugador (X o O) y da como resultado el valor numrico que
5.5.5. Defina el orden breadth-/irst (primero por amplitud) de los nodos de un rbol general representa cun "buena" parece ser la posicin para ese jugador (cuanto ms grande
corno la raz seguida por todos los nodos del nivel r, sguidos de todos lbs riodos del ni- sea el valor que d como resultado evaluate, mejor ser la posicin). Por supuesto,
vel 2 Y as sucesivamente. En cada nivel deben otderiarse los nodos de manera que los una posicin ganadora tendr el valor ms grande posible y una posicin perdedora
hijos del mismo padre aparezcan en el mismo orden en tjue"aparecen en el rbol, y sin I el menor valor posible. Un ejemplo de una funcin de evaluacin como sa para el
Y n2 tienen padres diferentes, n I aparece antes Que n2Si el padre den 1 aparece antes "gato" es el. nmero de renglones, columnas y diagonales restantes abiertas para un
que el padre.de n2; Extendeda definicin an bosque.-Escribaun programa en C que
jugador menos el nmero de las mismas para. su oponente (excepto que el valor 9
r~corra un bosque representadocomo un rbol binario en orden primero por amplitud.
sera el resultado para la posicin que gana y -.9 para la que pierde). Esta funcin
5.5.6. Considere el siguiente mtodo de transformacin de un rboI general, gt, en un rbol
no "prev" todas las posibles posiciones del tablero que podran resultar de la po-
estrictamente binario, bt. Cada nodo de gt est representado por tiria hoja de bt. Si gt
sicin real, slo evala una posicin esttica del .mismo. ,.,.
const~. de un solo nodo, bt consta de un solo nodo. En cSO contrari bt cnsta de un 1
nuevo nodo raz; un subrbol izquierdo lt y un subrbol derecho rt. /tes el rbol estric- Dada una posicin del tablero, el mejor movimiento siguiente est determina- 1
tamente binario formado de manera rectirsiva a partir del subrbol lllayor de gt, y rt es do por la consideraein de todos los movimientos posibles y las posiciones resultan-
el rbol estrictamente binario formado de manera recursiva a partir de gt sin su subr- tes. El movimiento seleccionado ser aquel que resulte en la posicin del tablero con 1

bol mayor. Escribir una rutin en C para convertir un rbol general en un rbol estric- mayor evaluacin. Tal anlisis, sin embargo, no conduce por fuerza al mejor movi- .
tamente binario. miento. La figura 5.6. l ilustra una posicin y los cinco posibles movimientos que
5.5.7. Escri_ba en Cuna funcin compute, que acepte un apuntador a un rbol que represente puede hacer X desde la misma. Aplicando la funcin de evaluacin que se acaba de
una expresin con operandos constantes y d el resi.Iltado de evaluar la expresin sin describir a las cinco posiciones resultantes, se llega a los valores mostrados. Cuatro
destruir el rbol. , , . movimientos producen la misma evaluacin mxima, aunque tres de ellos son sin
5.5.8~ Escriba'un _programa en C-par. convertir una expresin infij a una expresin de rbol. duda inferiores al cuarto. (La cuarta posicin produce una victoria segura para X,
Supngase que todos los operadores no binarios preceden a sus operandos. Sea entonces mientras que los otros tres pueden empatar con 0). En realidad, el movimiento que
_la representacin de la expresin de entrada como sigue: un operando est .representa- produce la evaluacin menor es tan bueno o mejor que los movimientos que produ-
do por el carcter 'N' seguido por un nmero, un _operador_por el cracter 'T' seguido cen una evaluacin ms alta. La funcin de evaluacin esttica, por consiguiente, no

316 Estructuras de datos en C Arboles 317


x1~10
vi

e,
+

"-
e,

vi
e,
2
~ 2
X
o
~x1~1
X O
2 2 Figura 5.6.1
'.::'.
.,.,
e: o: +
"- vi . es suficientemente buena para predecir el resultado del juego. Se puede generar una
o mejor funcin de evaluacin para el juego del "gato" (incluso mediante un mtodo
<., + de fuerza bruta que pone en una lista todas las posiciones y la respuesta apropiada),
c <., ~ pero muchos juegos son muy complejos para determinar la mejor respuesta con eva-
e,: vi luadores estticos.
00
+ Supngase que se pueden prever varios movimientos. Entonces la eleccin de
.-: :o. un movimiento puede perfeccionarse en gran medida. Defina el nivel de previsin
~

vi e

como el nmero de movimientos que se deben considerar en el futuro. Si se inicia en
cualquier posicin, se puede construir un rbol de todas las posiciones del tablero
o
posibles que pueden resultar de cada movimiento. Tal r.bol se llama rbol de juego.
"'~ El rbol de juego para la posicin de apertura del "gato" con un nivel de previsin
o.
..: s
* w igual a 2 se ilustra en la figura 5.6.2. (En realidad existen otras posiciones, pero, por
:...: 2 h vi ;;,
ze consideraciones de simetra stas son en efecto las mismas que las mostradas). Ad-
+
!::e 2 ~ virtase que el nivel mximo (llamado profundidad) de los nodos de un rbol de este

+ u tipo es igual al nivel de previsin.
.;- !::e o Desgnese al jugador que tiene que mover a partir de la posicin de juego de
+ .,; ~ la raz como ms y a su oponente como menos. Trtese de encontrar el mejor movi-
s .;- vi
"'
+ .,; miento para ms desde la posicin de juego de la raz. Los nodos restantes del rbol
s .,;
~
pueden designarse como nodos ms o menos, dependiendo de qu jugador debe mo-
..; ~
verse desde esa posicin del nodo. Cada nodo de la figura 5.6.2 est marcado con un
vi "' "'
:
" "' nodo ms o menos.
Supngase que las posiciones de juego de todos los hijos de un nodo ms han
sido evaluadas por el jugador ms. Es claro entonces que ms debe escoger el mov-
'"
.,; + miento que produce la evaluacin mxima. As, el valor de un nodo ms para el ju-
gador ms es el mximo de los valores de sus hijos. Por otra parte, una vez que ms
realiz un movimiertto, menos selecionar el movimiento que produzca menor eva-
ci luacin para el jugador ms. Por lo tanto, el valor de un nodo menos para el jugador
"- .,; "' ms es el mnimo de los valores de sus hijos.
"' En consecuencia, para decidir el mejor movimiento para un jugador ms desde
la raz, se deben evaluar las posiciones de las hojas para el jugador ms usando una
.,; funcin de evaluacin esttica. Despus, se mueven esos valores hacia arriba, en el
rbol de juego, asignando a cada nodo ms el mximo de los valores de sus hijos, y a
cada nodo menos, el mnimo de los valores de sus hijos, con la suposicin de que

318 Estructuras de datos en e Arboles 319


menos va a elegir el movimiento que es peor para ms. El valor asignado a cada no-
do de la figura 5.6.2 mediante este proceso se indica de inmediato debajo del nodo.
El movimiento que ms debe seleccionar, dada la posicin del tablero en el
nodo raz, es aquel que eleva al mximo su valor. As, el movimiento de apertura para
X debe ser en el cuadrado medio, como se ilustra en la figura 5.6.2. La figura 5.6.3
ilustra la determinacin de la mejor respuesta de O. Advirtase que la designacin de
"ms" y "menos" depende de a quien se le est determinando su movimiento. As,
en la figura 5.6.2, X est designado como ms, mientras que en la 5.6.3 es O. En la
aplicacin de la funcin de evaluacin esttica a una posicin del tablero, se calcula
el valor de la posicin para cualquier jugador designado con ms. Este mtodo se
llama el mtodo minimax, ya que, al subir al rbol, las funciones mximo y mnimo
se aplican de manera alterna.
El mejor movimiento para un jugador desde una posicin dada, puede deter-
minar construyendo primero el rbol de juego y aplicando una funcin de eva-
luacin esttica a las hojas. Entonces esos valores se mueven hacia arriba aplicando
el mnimo y el mximo en los nodos menos y ms, respectivamente. Cada nodo del
rbol de juego tiene que incluir una representacin del tablero y una indicacin de si
el nodo es un nodo ms o menos. Por lo tanto, los nodos pueden declararse por
medio de:
struct nodetype {
char board[3J[3J;
int turn;
struct nodetYpe * hij;
struct nodetype *prximo;
};

typedef struct nodetype *NODEPTR;

+ +

x=w=tt
+
** _;
X

-3
.
X X

_,
X

-3
X
-4 -.1

Figura 5.6.3 Computacin de la respuesta del jugador O.


-4
* -3

320 Estructuras de datos en C Arboles 321


p - > board[i]U] tiene el valor 'X', 'O' o' ', dependiendo de si el cuadrado en el NODEPTR ptree;
rengln y columna j de ese nodo est ocupado por alguno de los jugadores o est inti,j;
desocupado. p - > turn tiene el valor + l o - l, dependiendo de si el nodo es un
I * crear la raz del rbol e inicializarla */
nodo ms o menos, respectivamente. Los dos campos restantes de un nodo se usan
ptree = getnode();
para indicar la posicin del nodo en el rbol. p ,_ > son apunta al hijo mayor del
far (i=O i < 3; ++i)
nodo y p - > next a su siguiente hermano ms joven. Supngase que la declaracin for (j=O; j < 3; ++j)
anterior es global, que la lista disponible de nodos se ha establecido y que se han ptree->board[il[jl = brd[il[jl;
escrito las rutinas getnode y Jreenode adecuadas. /* la raz es un nodo ms por d'efinicn *I
La funcin next move (brd, player, look/evel, newbrd) en C determina el me- ptree->turn = 1;
jor movimiento siguiente. brd es un arreglo de 3 por 3 que representa la posicin ac- ptree->son = NULL;
tual del tablero; player es 'X' o 'O', dependiendo de qu movimiento se est calcu- ptree->next ~ NULL;
lando (advirtase que en el "gato" el valor de player puede calcularse a partir de /* creacin del' resto del rbol de juegos *!
brd, de manera que ese parmetro no es tan necesario), y looklevel es el nivel de pre- expand ( ptree, O, looklevel) ;
visin usado en la construccin del rbol. newbrd es un parmetro de salida que return(ptree);
representa la mejor posicin del tablero.que p/ayer puede alcanzar desde la posicin
/* fin de buildtree *I
brd.
nextmove usa dos rutinas auxiliares: buildtree y bestbranch. La funcin build- expand puede implantarse mediante la generacin de todas las posiciones del
tree construye el rbol de juego y da como resultado un apuntador a su raiz. La tablero que pueden obtenerse de la posicin del tablero apuntada por p y el estableci-
funcin bestbranch calcula el valor de dos parmetros de salida: bes/, que es un miento de sta como los hijos de p en el rbol de juego. Despus expand se llama a s
apuntador al nodo del rbol que representa el mejor movimiento, y value, que es la misma de manera recursiva usando esos hijos como parmetros hasta que se alcanza
evaluacin de dicho movimiento mediante la tcnica "minimax". la profundidad deseada. expand usa una funcin auxiliar genera/e, que acepta una
posicin del tablero brd y da como resultado un apuntador a una lista de nodos que
nextmove ( brd, looklevel, player, newbrd) contiene las posiciones del tablero que pueden obtenerse a partir de brd. Esta lista
char brd[ )[3], newbrd[ ][3J; est ligada por medio del campo next. La escritura de genera/e se deja como ejerci-
int looklevel; cio para el lector.
char player;
1 expand(p, plevel, depth)
NODEPTR ptree, best; NODEPTR p;
int i, j, value; int plevel, depth;
ptree = buildtree(brd, looklevel); {
bestbranch(ptree, player, &best, &value); NODEPTR q; li
for (i=D; i < 3; ++i) if (plevel < depth) { ,i
for (j=O; j < 3; ++j) I * p no es el nivel mXin I *
newbrd[i][jl = best->board[il[jl; g = generate(p->board);
I * fin de nextmove *I p->son = q;
while (q != NULL)
La funcin nextmove (brd, player, /ookleve/, newbrd) en C determina el me- I * recorrer la lista de nodos *I
juego y usa la funcin auxiliar getnode, que asigna memoria para un nodo y regresa if (p->turn == 1)
un apuntador al mismo. Tambin usa una rutina expand(p, plevel, depth), en la que q->turn -1;
else
pes un apuntador a un nodo del rbol de juego, p/evel es su nivel y depth es la pro-
fundidad del rbol de juego que se debe construir. expand produce el subrbol con
q->turn l; J
q->son = NULL;
raiz en p para la profundidad adecuada. expand(q, plevel+1, depth);
q = q->next;
NODEPTR buildtree(brd, looklevel) I * .fin de whJle * !
char brd[H3l; I* fin deH *!
int looklevel; /* /Jn de expand * I

Estructuras de datos en C Arboles 323


322
Ya que se cre el rbol de juego, bestbranch evala los nodos del mismo. if (val> *pvalue)
Cuando un apuntador a una hoja se transfiere a bestbranch, sta llama a una fun- *pvalue = val;
cin eva/uate que evala estticamente la posicin del tablero de esa hoja para el ju.. *pbest = p;
gador cuyo movimiento se est determinando. El cdigo de eva/uate queda como I* fin deif *I
ejercicio. Cuando se transfiere a bestbranch un apuntador a un nodo no hoja, la p = p->next;
rutina se llama a s misma de manera recursiva en cada uno de sus hijos y despus ! * fin de whle * /
asigna el mximo de los valores de sus hijos al nodo no hoja s ste es un nodo ms, o if (pnd->turn == -1)
*pvalue ~ -*pvalue;
el mnimo, si es un nodo menos. bestbranch tambin se encarga de no perder de vista
I* fin deif *!
qu hijo produjo este valor mximo o mnimo. I* fin de bestbranch *I
Si p - > turn es - l, el nodo apuntado por p es un nodo menos y se le debe
asignar el mnimo de los valores asignados a sus hijos. Sin embargo, si p - > turn es
+ l, el nodo apuntado por p es un nodo ms y su valor deber ser el mximo de los
valores a los hijos del nodo. Si mn(x,y) es el mnimo de x y y y mx(x,y) su mxi-
mo, entonces, mn(x,y) ; - mx - x, - y) (se invita a probar la afirmacin ante-
rior corno ejercicio trivial). As, el mximo o el mnimo correctos pueden encontrar- 5.6.1. Examine las rutinas . esta seccin y determinar si todos los p.rmetrds Son realmen-
se como sigue: en el caso de un nodo ms, calcular el mximo; en el caso de un nodo te necesarios. CmO se pueden revisar las listas de parmetros?
menos, ca.lcular el mximo de los negativos de los valores y cambiar el signo del re- 5.6.2. Escriba rutirias en C genrate y e.Vfuate como las descritas en el texto.
sultado. Estas ideas estn incorporadas en. bestbranch. Los parmetros de salida 5.6.3. Vuelva a escribir los provama~ de e~ta secin y de .Ia anterior bajo la implantacin
*pbest y *pvalue son, respectivamente, un apuntador a aquel hijo de la raz del rbol .en la que cada nodo del rbol incluye un campofather que contiene un aPuntador a
que aumenta al mximo su valor y el valor del hijo que ha sido asignado ahora a la su_p~dre. Bajo qu implantacin son rr1s eficaces, eStos progr.ma.s?
raz. 5.6.4. Es~riba versiones no recu,rsivas de las rutinas expand y bestbrnch proporcionadas. en
el texto.
bestbranch(pnd, player, pbest, pvalue) 5.6.5. Modifique la rutina bestbranch del texto de modo que los nodos delrbol sean libera-
NODEPTR pnd, *pbest; dos una vez que ya no se necesitan.
int *pvalue; 5.6.6. Combine el proceso de construir el rbol d~ juego y evaluar sus nodos en. un proceso
char player; nico, de tal manera que no se requiera_co.ntar co.n el rbo.l de jueg.o cqmp.leto y.se
1 pueda liberar sus nodos cuando no sean. necesarios. , '
NODEPTR p, pbest2;
5.6. 7. Modifique el programa del ejercicio previo de manera que si la evaluaci,n d.e.un nodo
int val;
menos es mayor que el mnimo de los valores de los hermanos mayor.es de su pad,re, el
programa no se preocupa en extender los hermanos ms jvenes de ese nodo menos, y '
iL (pnd->son = NULL) 1 si la evaluacin de un nodo ms es menor que el mximo de los valores que los herma- 1
I * pnd es una hoja *I nos mayores de su padre, el programa no se preocupa en extender los hermanos ms i
* pvalue = evalucin{ pnd->tablero, jugador); jvenes de ese nodo ms. Este mtodo se llama alfa-beta minimax. Explicar por qu
*pbest = pnd;
es correcto.
}
5.6.8. El juego de ka/ah se juega como sigue: dos jugadores tienen cada uno 7 orificios, seis
else 1
I * el nodo es una hoja, recorrer la lista de hijos *I de ellos se llaman pits y el sptimo se llama ka/ah. Estn dispuestos de acuerdo con el
siguiente diagrama.
p = pnd->son;
bestbranch{p, player, pbest, pvalue);
Jugador 1
*pbest = p
if (pnd.turn == -1)
*pvalue = -*pvalue; KPPPPPP
p= p->next; PPPPPPK
while (p != NULL) 1
bestbranch(p, player, &pbest2, &val); Jugador 2
if (pnd->turn == -1)
Al inicio hay seis piedras en cada "pit" y ninguna en kalah, por lo que la posicin de
val= -val;
apertura, se ve como sigue:

324 Estructuras de datos en C Arboles 325


0666666
6666660

Los jugadores se turnan y cada turno consiste en uno o ms movimientos. Para hacer
un movimiento, el jugador elige uno de sus pits no vaco. Las piedras se eliminan de
ese pit y se distribuyen en el sentido de las manecillas del reloj, dentro de los pits_ Yde
la "kalah" del jugador correspondiente (la kalah del oponente se salta) una piedra
por orificio hasta que no queda ninguna. Por ejemplo, si el jugador 1 es el primero en
mover, un movimiento de apertura posible dara como resultado la siguiente posicin
del tablero: Ordenamiento
177 7 77 O
6666660

Si la ltima piedra de un jugador va a parar en su propia "kalah", el jugador puede


hacer otro. m.ovimiento. Si va a parar en uno de sus pits vacos, esa piedra Y las del
oponente que estn. en el ''pit'' opuesto se e}iminan y se.colocan en la ''kalah'' del ju-
gador. El ju_ego termina cuando algn jugador ya _no tiene piedras en sus "pits". En
ese momento se colocan todas las _piedras del oponente en la "kalah" del oponente Y
termina ejuegO ...EI gan&dor es aqul"que tenga Il1s p_iedra:s en su "~a_lah".
Escriba un progr~~a que acepte la_ pos_icin de un tablero de "kalah"_ Yuna indica-
cin de quin est en turno y produzca el mejr movimiento para ese jugador.
5.6.9. Cmo pueden modificarse las ideas del programapara el juegd de "gato" con el
propsito de calcular el mejor movimiento en un juego que contenga un elemento de
azar, como el "bckgainmon"? La brsqueda y el ordenamiento estn entre los ingredientes ms comunes de los sis-
5.6.10. Por qu se han podido programar las computadoras para jugar de manera perfecta el temas de programacin. En la primera seccin de este captulo, discutimos algunas
"gato" pero no el ajedrez o las dama:s? de las consideradones globales que involucra el ordenamiento .. En el resto del
5.6.1 t. El j~g-de nim se juega como sigue: se c61ocan cierto nmero de palillos en una pila. captulo examinamos algunas de las tcnicas de ordenamiento ms comunes y las
Se alternan dos jugadores para eliffiinar uno o dos palillbs de la pila. El jugador que ventajas o desventajas que tienen unas con respecto a otras. En el captulo siguiente
elimina e! ltimo es el perdedor. Escriba una funcin en C para: determinar el mejor trataremos acerca de la bsqueda y algunas de sus aplicaciones.
movimiento en "nim".

6.1. ANTECEDENTES GENERALES

El concepto de un conjunto ordenado de elementos tiene un impacto considerable en


nuestra vida diaria. Considrese, por ejemplo, el proceso de encontrar un nmero de
telfono en un directorio. Este proceso, llamado bsqueda, se simplifica de manera
considerable por el hecho de que los nombres estn registrados por orden alfabtico
en el directorio. Imagnese el problema que tendra al intentar localizar un nmero
de telfono si los nombres aparecieran segn el orden en que los clientes solicitaron
su servicio a la compaa telefnica. En tal caso, los nombres tambin podran haber
sido registrados en orden aleatorio. El proceso de bsqueda se simplifica porque los
registros estn clasificados en orden alfabtico eh. vez de cronolgico. O considrese
el caso de una persona buscando un libro en una biblioteca. Como los libros estn
acomodados en un orden especifico (Librera del Congreso, Sistema Dewey, etc.), a
cada libro se le asigna una. posicin especifica respecto a los otros.y puede ser recupe-
rado en una cantidad razonable de tiempo (si est ah). O considrese un conjunto de

327
326 Estructuras de datos en C
nmeros ordenados en forma secuencial en la memoria de una computadora. Como acuerdo a las llaves numricas mostradas, el archivo que resulta es como el de la fi-
veremos en el captulo siguiente, por lo general es ms fcil encontrar un elemento gura 6.1.1 b. En este caso los propios registros han sido ordenados.
particular si el conjunto de nmeros se guarda clasificado segn un orden. En gene- Supngase, sin embargo, que la cantidad de datos almacenada en cada uno de
ral, un conjunto de artculos se guarda de manera ordenada con el fin de producir un los registros en el archivo de la figura 6.1.1 a es tan grande que la sobrecarga que
informe (para simplificar la recuperacin manual de informacin, como en un direc- implica mover los datos reales es prohibitiva. En ese caso, puede usarse una tabla
torio telefnico o en la estantera de una biblioteca) o para hacer ms eficente el auxiliar de apuntadores de manera que esos apuntadores sean movidos en lugar de
acceso a los datos en una mquina. los datos reales, como se muestra en la figura 6.1.2. (Esto se llama ordenamiento por
Presentamos ahora alguna terminologa bsica. Un archivo de tamao n es direccin.) La tabla en el centro es el archivo y la de la izquierda es la tabla inicial de
una secuencia den elementos r[OJ, r[l], ... , r[n l]. Cada elemento en el archivo apuntadores. La entrada en la posicin j en la tabla de apuntadores apunta al
se llama un registro. (Los trminos archivo y registro no se estn usando aqu para registro j. Durante el proceso de ordenamiento, las entradas en la tabla de apuntado-
referirnos a una estructura de datos especfica, como en la terminologa en C. En lu- res se ajustan de tal manera que la tabla final es como la que se muestra en la figura
gar de ello, los usamos en un sentido ms general). A cada registro r[i] est asociada de la derecha. Al inicio, el primer apuntador sealaba hacia la primera entrada en el
una llave, k[i]. Por lo regular (pero no siempre) la llave es un subcampo del registro archivo; al terminar el primer apuntador seala a la cuarta entrada de la tabla. Ob-
entero. Se dice que el archivo est ordenado de acuerdo a la llave, si i < j implica srvese que no se mueve ninguna de las entradas originales del archivo. En muchos
que k[i] precede a kUJ para algn ordenamiento de las llaves. En el ejemplo del de los programas en este captulo ilustramos tcnicas de ordenamiento de registros
directorio telefnico, el archivo consta de todas las entradas del libro. Cada entrada reales. La extensin de estas tcnicas para ordenamiento por direccin es rectilnea y
es un registro. La llave de acuerdo a la cual est ordenado el archivo es el campo de se dejar como ejercicio al lector. (En realidad, ordenamos slo las llaves en los
nombres del registro. Adems, cada registro contiene campos para la direccin y ejemplos de este captulo en aras de obtener simplicidad; dejamos allector la modifi-
el nmero de telfono. . cacin de los programas para ordenar registros completos.)
Un ordenamiento se puede clasificar como interno si los registros que se estn Dada la relacin entre bsqueda y ordenamiento, la primera cuestin que se
ordenando estn en la memoria principal, o externo si algunos de los registros que se debe plantear en cualquier aplicacin es si debera o no ordenarse- un archivo. En
estn ordenando estn en el almacenamiento auxiliar. Restringimos nuestra atencin ocasiones, buscar un elemento particular en un conjunto implica menos trabajo que
a los ordenamientos internos. ordenar primero el conjunto completo para despus extraer el elemento deseado.
Es posible que de dos registros de un archivo tengan la misma llave. Una tcni- Por otra parte, si se requiere del uso frecuente del archivo con el propsito de recu-
ca de ordenamiento se llama estable si para todos los registros i y j tales que k[i] sea perar elementos especficos, podra ser ms eficiente ordenarlo primero. Esto ocurre
igual a kUJ, si r[i] precede a rUJ en el archivo original, entonces r[i] tambin precede porque la sobrecarga que implican las bsquedas sucesivas puede exceder en mucho J;.
a rUJ en al archivo ordenado. Es decir, un ordenamiento estable mantiene los a la que implica ordenar antes el archivo para luego recuperar del mismo los elementos ii
registros con llaves iguales en el mismo orden relativo en el que estaban antes del correspondientes. As, no puede decirse queesms eficiente ordenara no ordenar. '1
ordenamiento. El programador debe tomar una decisin basndose en circunstancias individuales.
Un ordenamiento ocurre ya sea sobre los mismos registros o sobre una tabla Una vez tomada la decisin de ordenar, tienen que tomarse otras decisiones, inclu-
auxiliar de apuntadores. Por ejemplo, considrese la figura 6.1.la en la cual se yendo qu debe ser ordenado y cules mtodos deben usarse. No hay un mtodo de
muestra un archivo de cinco registros. Si se ordena el archivo en orden ascendente de ordenamiento que sea universalmente superior a todos los otros. El programador
Llaves Otros campos
.
Tabl Tabia
1 original de ordend de
Registro l 4 VDD t AAA
apuntadores Archivo apuntadores
Registro:! 2 888 2 B81J Registro l 4 DDD
.

Registro 3 t AAA 3 CCC Registro 2 2 888

Registro 4 5 U,E 4 DDD Registro 3 1 AAA


Registro 5 3 CCC 5
.
U,E Registro 4 5 EEE
Archivo Archivo
Figura 6.1.1 Ordenamiento Registro 5 3 CCC
(a) Archivo original, (b) Archivo ordenado de registros reales.

Figura 6.1.2 Ordenamiento usando una tabla auxiliar de apuntadores.

328 Estructuras de datos en C Ordenamiento 329


debe examinar el problema de manera cuidadosa, as como los resultados deseados, misma constante de proporcionalidad = l. As, si y es proporcional ax, duplicar x
antes de decidir estas cuestiones tan importantes. duplicar ay, y multiplicar x por !O multiplicar y por JO. De manera similar, siy es
proporcional ax, duplicar x multiplicar a y por 4 constante de proporcionalidad =
Consideraciones de eficiencia 2 y multiplicar x por JO multiplicar y por 100.
Con frecuencia, no medimos la eficiencia en cuanto a tiempo de un ordena-
Como veremos en este captulo, hay un gran nmero de mtodos que pueden miento por el nmero de unidades de tiempo requeridas sino por el nmero de
usarse para ordenar un archivo. El programador debe estar enterado de varias consi- operaciones crticas ejecutadas. Ejemplos de tales operaciones crticas son compara-
deraciones de eficiencia que se interrelacionan y con frecuencia entran en conflicto ciones de llaves (es decir, las comparaciones de las llaves de dos registros en el archi-
para hacer una eleccin inteligente del mtodo de ordenamiento ms apropiado a un vo para determinar cul es mayor), movimientos de registros o apuntadores a
problema particular. Tres de las consideraciones ms importantes, entre esas, inclu- registros, o intercambio de dos registros. Las operaciones crticas elegidas son
yen la cantidad de tiempo que debe invertir un programador para codificar un aquellas que toman ms tiempo. Por ejemplo, una comparacin de llaves puede ser
programa particular de ordenamiento, la cantidad de tiempo de mquina necesaria una operacin compleja, en especial si las propias llaves son largas o el orden entre
para que el programa corra y la cantidad de espacio necesaria para el programa. ellas no es trivial. As, una comparacin de llaves requiere mucho ms tiempo.que,
Si un archivo es pequeo, por lo regular ocurre que tcnicas de ordenamiento digamos, un simple incremento de una variable ndice en una iteracinfor. Tambin,
complejas, diseadas para minimizar los requerimientos de espacio y tiempo, son el nmero de operaciones simples requerido es, por lo regular, proporcional al n-
peores o mejores slo por un margen pequeo en el logro de eficiencia que mtodos mero de comparaciones de llaves. Por esta. razn, el nmero de. comparaciones .de
ms simples y, por lo general, menos eficientes. De manera similar, si un programa llave es una medida til de laeficiencia en tiempo de un ordenamiento.
particular de ordenamiento debe correrse slo una vez y hay suficiente tiempo y Hay dos maneras de determinar los requerimientos de tiempo de un ordena-
espacio de mquina para ello, sera ridculo que un programador invirtiera das in- miento, ninguna de las cuales produce resultados que sean aplicables a todos los
vestigando los mejores mtodos para obtener el ltimo gramo de eficiencia. En tales casos. Un mtodo es hacer el anlisis matemtico y a veces intrincado de varios casos
casos, la cantidad de tiempo que debe invertir el programador es, de manera primor' (por ejemplo, mejor caso, peor caso y caso promedio). El resultado de este anlisis es
dial, para la consideracin de d.eterminar qu mtodo de ordenamiento usar. Sin con frecuencia una frmula que da el tiempo promedio (o nmero de. operaciones)
embargo hay que tener mucho cuidado. El tiempo de programacin nunca es una ex, requerido por un ordenamiento particular como funcin del tamao del archivo n.
cusa vlida para usar un programa incorrecto. Un ordenamiento que se corra una (En realidad, los requerimientos de tiempo de un ordenamiento dependen de otros
sola vez puede darse el lujo de una tcnica ineficiente, pero no de. una incorrecta. factores ms que del tamao del archivo; sin embargo, aqu nos ocupamos slo de la
Los datos presumiblemente ordenados pueden usarse en una aplicacin en la que la dependencia del tamao del archivo.) Suponer un anlisis matemtico de ese tipo de.
11
suposicin de datos ordenados sea cruci.al. un programa de ordenamiento particular que lleva a la conclusin de q~e el progra- 11

Sin embargo, un programador .debe ser capaz de. reconocer el hecho de que un ma toma un tiempo de ejecucin de O.O! n 2 + IOn unid.ades. Las columnas primera y ,,,,
1
1,

ordenamiento particular sea ineficiente y dejustificar su uso en una situacin par- cuarta de la figura 6.1.3 muestran el tiempo necesario para el ordenamiento de va
ros valores den. Se notar que para valores pequeos den, la cantidad IOn (tercera,
0
:
ticular. Muy.a menudo, los programadores toman la va fcil y programan un orde- '
namiento ineficiente, que se incorpora despus a un sistema mayor en el cual dicho columna de la figura 6.1.3) sobrepasa la cantidad 0.01 n 2 (segunda columna}. Esto
ordenamiento es u.n componente llave. Los diseadores y planeadores del sistema se ocurre porque la diferencia entre n 2 y n es pequea para valores pequeos den y est
sorprenden despus de lo inadecuado de su creacin. Para maximizar su eficiencia, ms que compensada por la diferencia ntre 10 y O.O!. As, para valores pequeos de
un programador tiene que conocer un amplio rango de tcnicas de ordenamiento as n, un incremento den por el factor 2 (por ejemplo de 50 a 100) aumenta el tiempo
como sus ventajas y desventajas, de tal manera que cuando surja la necesidad de un necesario para el ordenamiento por el mismo factor 2 aproximadamente (de 525 a
ordenamiento pueda suministrar la ms apropiada para la situacin particular. 1100). De manera similar, un incremento den por el factor 5 (por ejemplo, de 10
Esto nos lleva a las otras dos consideraciones de eficiencia: tiempo y espacio. a 50) aumenta el tiempo necesario para el ordenamiento por el factor 5, ms o menos
Como en la mayora de las aplicaciones en computacin, el programador tiene, con (de 101 a 525).
frecuencia, que optimizar una de esas consideraciones en detrimento de la otra. En Sin embargo, cuando n se hace ms grande, la diferencia entre n 2 y n crece tan
la consideracin del tiempo necesario para ordenar un archivo de tamao n no tratamos rpido que compensa al final la diferencia entre 10 y 0.01. As, cuando n es igual a
con unidades de tiempo reales, ya que stas variarn de una mquina a otra, de un 1000 los dos trminos contribuyen de la misma manera a la cantidad de tiempo nece-
programa a otro y de un conjunto de datos a otro. En lugar de ello, estamos intere- saria para el programa. Cuando n se hace an ms grande, el trmino 0.01 n 2 rebasa
sados en el cambio correspondiente de la cantidad de tiempo requerido para ordenar el trmino lOn y la comribucin de IOn se vuelve casi insignificante. As, para valo-
un archivo inducido por un cambio en el tamao del mismo, n. Veamos si podemos res grandes de n, uh incremento de n por el factor 2 (por ejemplo, de 50000 a
hacer este concepto ms preciso. Decimos que y es proporcional a x si la relacin 100000) resulta en un aumento del tiempo de ordenamiento cercano a 4 (de 25.5
entre y y x es tal que la multiplicacin de x por una constante multiplica a y por la millones a 101 millones) y un incremento den en el factor 5 (por ejemplo, de 10000 a

Estructuras de datos en C Ordenamiento 331


330
50000) lo incrementa en aproximadamente un factor de 25 (de 1.1 millones a 25.5 todo n 2: 1. (De hecho, el valor de b o n es irrelevante, puesto que el valor de una fun-
millones). En realidad, cuando n se vuelve cada vez mayor, el tiempo de ordena- cin constante es independiente de n .)
miento se vuelve cada vez ms proporcional a n 2, como se ilustra claramente en la Tambin es fcil mostrar que la funcin e * n es O(nk) para cualesquier cons-
ltima columna de la figura 6.1.3. As, paran grande, el tiempo requerido por el tantes e y k, Para verlo, ntese simplemente que e* n es menor o igual que e nk pa-
ordenamiento es casi proporcional a n 2 Por supuesto, para valores pequeos den, ra todo n 2: l (es decir, hacer a = e, b = 1). Tambin es obvio que nk es O(nk+1)
el ordenamiento puede exhibir un comportamiento muy diferente (como en la figura para todo j 2: O (usar a = I, b = I),' De la misma manera podemos mostrar que si
6.1.3), situacin que tiene que ser tomada en cuenta en el anlisis de su eficiencia. . J(n) y g(n) son ambas O(h(n)), la nueva funcinf(n) + g(n) es tambin O(h(n)).
Todos esos hechos juntos pueden usarse para mostrar que si f(n) es un polinomio
Notacin O contrminoprincipaldeordenk[esdecir,f(n) = c 1 nk + c 2 nk-l +.,, + ck*n
+ ck+ilJ(n) esO(nk). En realidad,f(n) es.O(nk+J) para todoj 2: O.
Introducimos alguna terminologa y una nueva notacin para captar el concep- Aunque una funcin puede ser asintticamente acotada por muchas otras
to de una funcin que se vuelve proporcional a otra cuando crece. En el ejemplo pre- [como por ejemplo, I0n 2 + 37n + 153 es O(n 2), O(I0n 2), 0(37n 2 + lOn) y
vio, se dice que la funcin O.Oln 2 + 10n es "del orden de" la funcin n 2, porque 0(0.05n 3)], por Jo regular buscamos una cota asinttica que sea un slo trmino con
cuando n se hace grande, la funcin se vuelve casi proporcional a n 2 coeficiente principal igual a 1 y que sea "tan cercana" como sea posible, As
Para ser precisos, dadas dos funcionesf(n) y g(n), decimos quef(n) es del or- diramos qu_e l0n 2 + 37 n + 153 es O(n 2), aunque tambin est asintticamente aco-
den de g(n) o quef(n) es O(g(n)) si existen enteros positivos a y b tales quef(n) :5 a* tada por muchas otras funciones. De manera igual nos gustara encontrar una fun-
g(n) para todo n 2: b. Por ejemplo, sif(n) = n 2 + 100n y g(n) = n2,j(n) es O(g(n)), cin g(n) tal quef(n) fuese O(g(n)) y g(n) fuese O(J(n)). Sif(n)es una constante o
ya que n 2 + lOOn es menor o igual que 2n 2 para todo n mayor o igual que 100. En un polinomio, esto puede hacerse siempre usando su trmino principal con coefi-
este caso a es igual a 2 y bes igual a 100. Del mismo modof(n) es tambin O(n 3), da- ciente l. Para funciones ms complejas, sin embargo, no siempre es posible
do qu n 2 + lOOn es menor o igual que 2n 3 para todo n mayor o igual que 8. Dada encontrar un ajuste fijo como ese. '
una funcinf(n), puede haber muchas funciones g(n) tales quef(n) sea O(g(n)). Una funcin importante en el estudio de la eficiencia de un algoritmo .es la fun-
Sif(n) es O(g(n)),f(n) se vuelve "finalmente" (es decir, paran 2: b) en forma cin logartmica. Recordar que logm n es el valor x para el cuatmx es igual a n. m se
permanente menor o igual que algn mltiplo de g(n). En un sentido estamos dicien- llama la base del logaritmo. Considerar las funciones logm n y logk n. Sea xm el logm
do que f(n) est acotada por arriba mediante g(n) o quef(n) es una funcin "me' n y xk el logk n. Entonces
nor" que g(n). Otra manera formal de decir esto es quef(n) est asintticamente
acotada por g(n). Otra interpretacin es quef(n) crece ms despacio queg(n), dado
que, en proporcin (es decir a partir del factor a), g(n) se vuelve al final ms grande. de manera que
Es fcil mostrar que si f(n) es O(g(n)) y g(n) es O(fz(n)), entonces f(n) es ''mxm = k\'k
O(h(n)). Por ejemplo, n 2 + lOOn es .O(n 2) y n 2 es O(n 3) (para ver esto hacer a y b
iguales a 1): en consecuencia n 2 + IOOn es O(n 3). Esta propiedad se llama propiedad Tomando log,,, en ambos lados,
transitiva. xm = logm(k-'k)
Obsrvese que sif(n) es una funcin constante [esdecir,f(n) = e para todo n], Ahora se puede mostrar fcilmente que log, (x') es igual ay* log,x para todax, y y
f(n) es O(!), dado que haciendo a igual a e y b igual a 1, tenemos que e :,; e 1 para z, de manera que la ltima ecuacin se puede volver a escribir como (recuerde que
xm = log 111 n)
n a= 0.01n 2 b = IOn __,_._,,_,_
a+b (a+b)
log 111 n = xk * log 111 k
10 1 100 101 1.01 o como (recordar que xk = logk n)
50 25 500 525 0.21
100
500
100
2,500
1,000
5,000
1,100
7,500
0.11 /ogm n = (log,,, k) * logk n
0.03
1.000 10.000 . 10,000 20,000 0,02
s.ooo 250.000 50,000 300,000 0,01
As, logm nm y logk n son mltiplos constantes cada un de la otra.
10.000 1,000,000 100.000 1,100,000 0,01 Es fcil mostrar que si f(n) = e * g(n ), donde e es una constante, f(n) es
50,000 25,000,000 500,000 25,500,000 0.01 O(g(n)) [en reali4ad ya habamos mostrado que esto es cierto para la funcinf(n) =
100,000 100,000,000 1,000,000 1O1,000,000 0.01
500,000 2,500,000,000 5,000,000 2,505,000,000
nk]. As log,,, n es O(log, n) y logk n es O(log,,, n) para toda m y k. Como_ cada fun-
0.01
cin logartmica es del orden de cualquier otra, por lo regular omitimos la base
Figura 6.1.3

332 Estructuras de datos en C Ordenamiento 333


cuando hablamos de funciones de orden logartmico y. decimos que todas ellas son Eficiencia de un ordenamiento
O(log n).
Con este concepto de "es de orden de" para un ordenamiento, podemos com-
Los siguientes planteamientos establecen un orden jerrquico de funciones: parar varias tcnicas y clasificarlas como "buenas" o "malas" en trminos genera-
les. Podra esperarse descubrir el ordenamiento "ptimo" (que sea O(n) sin impor-
e es 0(1) para toda constante c. tar el contenido o el orden de la entrada. Sin embargo, desafortunadamente puede
e es O(log n), pero log n no es 0(1). mostrarse que no existe tal ordenamiento que sea til siempre. La mayora de los or-
denamientos clsicos que consideraremos tienen requerimientos de tiempo que van
e* logn n es O(log n) para cualesquier constantes e, k. de O(n log n) a O(n 2). En el primero, la multiplicacin del tamao del archivo por
e* logk n es O(n), pero n no es O(log n). 100 multiplicar el tiempo de ordenamiento por algo menos que 200; en el ltimo, la
multiplicacin del tamao del archivo por 100 multiplica el tiempo de ordenamiento
c * nk es O(nk) para cualesquier constantes e, k. por un factor de 10000. La figura 6.1.4 muestra la comparacin den log n con n 2
e* nk es O(nk+I), pero nk+I no es O(nk).
para un rango de valores de n. Puede verse en la figura que para n grande, cuando se
incrementan, n 2 aumenta a un ritmo mucho ms rpido que n log n. Sin embargo,
e* n * logk n es O(n log n) para cualesquier constantes e, k. no debera seleccionarse una tcnica de ordenamiento por el simple hecho de ser O(n
c.* n * logk n es O(n 2), pero n 2 no es O(n log n). log n). La relacin del tamao n del archivo y de los otros trminos que constituyen
el tiempo de ordenamiento real deben conocerse. En particular, los trminos que
e* n1 * logk n es O(nl log n) para cualesquier constantes e, j, k. juegan un papel insignificante para valores grandes de n pueden tener un papel
e * n 1 * logk n es O(nl+ 1), pero i,I+ 1 no es O(nl log n). dominante para valores pequeos de n. Todos estos resultados deben considerarse
antes de que pueda hacerse una seleccin inteligente del ordenamiento.
e* ni* (logk n) 1 es O(nl (log n)') para cualesquier constantes e, J, k y/. Un segundo mtodo para determinar los requerimientos de tiempo de una tc-
nica de ordenamiento es correr en realidad el programa y medir su eficiencia (ya sea
e* ni* (logk n) 1 es O(nl+ 1) pero ni+ 1 no es O(nl (log n)),
midiendo las unidades de tiempo absolutas o el nmero de operaciones ejecutadas).
e* ni* (logk n) 1 es O(ni (log n) 1+ 1) pero ni (logk n) 1+ 1 no es O(nl (log n)'). Para usar tales resultados en la medicin de la eficiencia de un ordenamiento, la
e* nk es O(d") pero d" no es O(nk) para cualesquier constantes e y k, y d > l. prueba tiene que correrse en "muchos" archivos muestra. Aun cuando se recojan
tales estadsticas, la aplicacin de ese ordenamiento a un archivo especficopuedeno 11
producir resultados que sigan el patrn general. Atributos peculiares del archivo,.en
La jerarqua de funciones establecida por lo anterior, con cada funcin de orden cuestin pueden hacer que la velocidad del ordenamiento se desve de manera signi-
I'

;n~nor que la que le sigue, es e, log n, (log n)k, n, n, (log n)k, nk, nk, (log n)', nk+ 1 y
ficativa. En los ordenamientos de las secciones siguientes daremos una explicacin 1
intuitiva de porqu un ordenamiento particular se clasifica como O(n 2) o O(n log
. L~s funciones que son O(nk) para alguna kse dice que son de orden polino-
n); dejamos el anlisis matemtico y la verificacin sofisticada de datos empricos
mwl, mientras que las funciones ,que son O(d") para alguna d > pero no O(nk)
para cualquier k se dice que son de orden exponencial. como un ejercicio para el lector ambicioso.
En muchos casos el tiempo necesario para un ordenamiento depende de la se-
La distincin entre funciones de orden polinomial y exponencial es muy impor-
tante. Incluso una funcin de orden exponencil pequeo, como 2", crece mucho cuencia original de los datos. Para algunos ordenamientos, los datos de entrnda que
s
ms que cualquier funcin de orden polinomial como nk, sin importar el tamao de n log 10 n
n ''
k. Con_io ilustracin d~ la rapidez con que crece una funcin de orden exponencial, j'
cons1derese que 2 10 es igual a 1024 pero 2 10 (es decir, 1024 1) es mayor que el nme- J X !0 1 1.0x 10 1 1.0x 10 2
5 X JQI 8.5 X 10 1 2.5 X 10 3 1
ro formado por un I seguido de 30 ceros. El menor k para el cual IOk excede a 210 es
4, pero el menor k para el cual IOOk excede a 2 10 es 16: Cuando n se hace ms gran-
J X }Q 2
5 X !0 2
2.0x 10 2
} .3 X JQ 3
1.0x 104
. 2.5x 105 .~
"
de, se necesitan valores de k mayores para que nk no quede rezagado de 2". Para J X to 3 3.0x!0 3 LO x 10 6
5 X }Q 3 l.8x 10 4 2.5 X }Q 7
cualquier k fijo, 2" finalmente se hace de manera permanente ms grande q~e nk. J X JQ 4 4.Q X 104 J.Q X 10 8
Dado el increble grado de crecimiento de las funciones de orden exponencial 5 X JQ4 2.3 X 10 5 2.5 X JQ 9
los problemas.que requieren de algoritmos con tiempo exponencial para su soluci~ l X JQ 5 5.Q X JQS J.Ox!'0 1
5X JQ.S 2.8 X 106 2.5 X !QH
s~ consideran m/Jatab/es con el equipo de cmputo actual; es decir, problemas de ese J X JQ 6 6. X !Q6 ! .Q X JQ 12 Figura 6.1.4 Una
tipo no pueden resolverse con precisin excepto eri los casos ms simples. 3_3 X JQ 7 2.5 X \Q 13 comparacin de 'n 1.og n y n 2
5 X JQ6
J X !07 7,Q X JQ 7 J . X JQ 14 para varios valores de n.

334 Estructuras de datos en C 335


Ordenamiento
estn casi ordenados pueden terminar de ordenarse en tiempo O(n), mientras qne los tiempo mayor y, por lo tanto, requieren ms tiempo para ordenar conjuntos
que estn en orden inverso requieren de un tiempo O(n 2). Para otros ordenamien- pequeos.
tos, el tiempo requerido es O(n log n) sin importar el orden original de los datos. En las secciones restantes investigamos algunas de las tcnicas ms populares
As, si tenemos algn conocimiento acerca de la secuencia original de los datos podemos de ordenamiento e indicamos algunas de sus ventajas y desventajas.
tomar una decisin ms inteligente acerca de qu mtodo de ordenamiento elegir.
Por otra parte, si no tenemos tal conocimiento podemos desear seleccionar un orde-
namiento basado en el peor caso posible o en el caso "promedio". En cualquier
caso, el nico comentario general que puede hacerse acerca de tcnicas de ordena- 6.1.1. E,lija una tcnica de ordenamiento con la que se est familiarizado.
miento es que no existe una tcnica gen.eral "mejor" de ordenamiento. La eleccin a. Escribir un programa para el ordenamiento.
de una de ellas debe depender necesariamente de las circunstancias especficas. b. Es estable dicho ordenamiento'!
Una vez que ha seleccionado una tcnica de ordenamiento particular, el c. Determinar los requerimientos de tiemp de ordenamiento como una funcin del
programador debe proceder a hacer el programa tan eficiente como sea posible. En tamao del archivo, de manera matemtica y emprica.
muchas aplicaciones de programacin es necesario sacrificar eficiencia para obtener d. De qu orden es el ordenamiento?
claridad. Con los ordenamientos, la situacin es por lo general opuesta. Una vez que e. A partir de qu tamao del archivo comienza el trmino ms dominante a eclip:-
el programa para el ordenamiento ha sido escrito y probado, la meta principal del sar a los otros?
programador es mejorar su velocidad aunque se vuelva menos legible. La razn para 6. J.2. Muestre que la futici6n (logm n)k es O(n) para t~do m y k pero n no es O((log nk)
esto es que un ordenamiento puede ser responsable de la mayor parte de la eficiencia para cualquier k.
de un programa, de manera que cualquier perfeccionamiento en el tiempo. de orde- 6.1.~. Suponga que un cierto requerimien:t de tiempo est dad 'por la f~mula a* n2 + b *
namiento afecta de modo significativo la eficiencia global. Otra razn es que un or- n * log 2 n, donde a y b son constantes. Contestar las siguentes preguntas pro-
bando los resultados de manera matemtica y escribiendo un programa para
denamiento se usa por lo general con bastante frecuencia,, por lo que un pequeo
validar dichos resultados de manera emprica.
aumento en su velocidad de ejecucin ahorra una gran cantidad de tiempo de a. Para qu valores den (expresado en trminos de a y b) el primer trmino
computacin. Normalmente, es una buena idea eliminar los llamados a funciones, . domina al segundo?
en especial si stos se encuentran en ciclos internos, y remplazarlos por el cdigo de b. Para qu valor den (en trminos de a y b) los dos trminos son iguales?
la funcin en lnea, dado que el mecanismo llamada-retorno de un lenguaje puede c. Para qu valores den (expresado en trminos de a y b) ei segundo trmi-
ser muy costoso en trminos de tiempo. Tambin, una llamada a una funcin puede no domina al primero?
implicar la asignacin de memoria a variables locales, una actividad que requiere a 6. 1.4. Muestre qe cualquier proceSo que ordene un archivo puede extenderse para en-
veces una llamada al sistema operativo. En muchos de los programas no se hace as contrar todos los duplicados en el archivo.
para no oscurecer el objetivo del programa con enormes bloques de cdigo. 6.1.5. Un rbol de decislnpaia ordenamiento es un rbol binario que representa un mto-
Las restricciones de espacio son por lo general menos importantes que las con- do de ordenamiento basado en comparaciones. La figura 6 .. 1.5 ilustra tal rbol de
sideraciones de tiempo. Una razn para ello es que para la mayor parte de los decisin para un archivo de tres elementos. Cada no hoja de un rbol de ese tipo
programas de ordenamiento, la cantidad de espacio requerida es ms prxima a representa una comparacin entre dos elementos. Cada hoja representa un archivo
O(n) que a O(n 2). Una segunda razn es. que si se requiere de ms espacio, ste ordenado por completo. Una rama izquierda de una no hoja indica que la primera lla-
puede casi siempre encontrarse en la memoria auxiliar. Un ordenamiento ideal es un ve fue ms pequea que la segunda; una rama derecha indica que fue ms grande.
ordenamiento en el lugar cuyos requerimientos de espacio adicional son 0(1). Es de- (Suponemos que todos los elemntos del archivo tienen'Uaves distintas.) Por ejemplo,
cir, un ordenamiento en el lugar manipula los elementos que deben ser ordenados el rbol de la figura 6.1.5 representa un ordenamiento de tres elementos x[O], x[I],
x[2] que procede como sigue:
dentro del espacio de la lista o arreglo que contiene la entrada original no ordenada.
Comparar x[O] con x[l]. Six[OJ < x[l], comparar x[!J conx[2], y six[I] < x[2], el ti-
Cualquier espacio adicional requerido est en la forma de un nmero constante de
po de ordenamiento del archivo es x[O], x[l], x[2]; en.otro caso si x[O < x[2] el tipo
localidades (tales como las variables individuales declaradas de un programa), sin de ordenamiento serx[O], x[2], x[l], y si x[OJ > x[2], el tipo de ordenamiento ser
importar el tamao del conjunto que ha de ser ordenado. x[2], x[O], x[l]. Si x[O] > x[l], proceder de manera similar hacia abajo del subrbol
Por lo general, la expectativa de relacin entre tiempo y espacio se mantiene derecho.
para los algoritmos de ordenamiento: aquellos programas que requieren menos a. Muestre un rbol de decisin para ordenamiento que nunca haga comparaciones
tiempo requieren ms espacio y viceversa. Sin embargo, existen algoritmos muy redundantes (es decir, que nunca compare i[iJ y xU] si se conoc la relacin entre
diestros que usan tanto tiempo como espacio mnimos; es decir, son ordenamientos ellos). Tien~ n !- hojS.
en el lugar y O(n lag n). Sin embargo, stos pueden requerir ms tiempo del progra- b. Demuestr que l profundidad de tal rbol de decis'in es al menos Jog 2 (n !).
mador para desarrollar y verificar. Tienen tambin constantes de proporcionalidad c. Demuestre que n ! =::: (n/2) 1112 , de tal-manera que la profundidad de dicho rbol es
ms altas que muchos ordenamientos que usan ms espacio o que tienen orden de O(n lag n).

336 Estructuras de datos en C Ordenamiento 337


En cada uno de los ejemplos subsecuentes, x es un arreglo de enteros de los
cuales los n primeros deben ser ordenados de manera que x[i] :5 xU] para O :5 i < J
< n. Es fcil extender este simple formato a uno que se use en el ordenamiento den
registros, cada uno con una llave de subcampo k.
La idea bsica subyacente en el ordenamiento de burbuja es pasar a travs del
archivo varias veces en forma secuencial. Cada paso consiste en la comparacin de
cada elemento en el archivo con su sucesor (x[i] con x[i + !]) y el intercambio de los
dos elementos si no estn en el orden correcto. Considrese el siguiente archivo:

Figura 6.1.5 Un rbol de decisin para 25 57 48 37 12 92 86 33


2<3<1 3<2'<1
un archivo de tres_ elementos.
En el primer paso se hacen las siguientes comparaciones:
d. EXp'ue por qu esto prueba que cualquier mtod de_ ordenamiento que use x[O] con x[ I] (25 con 57) no intercambio
comparaciones en un archivo de tarriao n tiene que ha_cr al me~os O,(n log_ n)
comparaciones. X[ J] con x[2] (57 con 48) intercambio
6.1.6. Dado un rbol de decisin para ordenamiento de un archivo corra en el ejercicio . x[2] con x[3] (57 con 37) intercambio
preVio, mostrar _que si el archivo contiene a1gtlOs 'elementos iguales,_ el resultado d la
aplicadn del rb(?l .al. archi_vo (donde se to~- ulla_ ram~ _izquierda o una derecha x[3] con x[ 4] (57 con 12) intercambio
cuando dos elementos sean iguales) es un archivo ordenado.
x[ 4] con x[5] (57 con 92) no intercambio
6.1. 7-. Extienda el concepto de un tbof de_ decisin _binario d_e los ejercicios previos a un r-
bol ternari que incluya la posibilidad de igualdad. Se ,desea determinar cules ele- x[5] con x[6] (92 con 86) intercambio
mentos del archjvo. son .iguales, en adicin al orden de. los distintos. elementos del
mismo. C.lntas con:i,paraciones son necesarias? x[6] con x[7] (92 con 33) intercambio 1

6. 1.8. Demuestre qe si k es el menor entero mayor o igual a n + lof n --2, son necesa- As, despus del primer paso, el archivo est en el siguiente orden:
rias y suficientes k comparaciones para. encontrar el primero y segundo elementos ms ;1
grandes de un conjunto den elemeiltos_distintos. 25 48 37 12 57 86 33 92 t !
,' !

6.1.9. Cuntas comparaciones se requieren pa_ra encontrar el elemento mayor y el menor


de un conjunto de.n elementos distinto~? Obsrvese que despus del primer paso, el elemento mayor (en este caso 92) es-
6. t. 10. Muestre que la funcin J(n) definida por t en la posicin correcta dentro del arreglo. En general, x[n - i] estar en su posi-
cin adecuada despus de la iteracin i. El mtodo se llama ordenamiento de burbu-
/(1) = l ja porque cada nmero "burbuja" con lentitud hacia su posicin correcta. Despus
del segundo paso el archivo ser:
= f(n - 1) + 1/n paran > 1

,
/(il)

es O(log n). 25 37 12 48 57 33 86 92

Obsrvese que ahora 86 encontr su lugar en la segunda posicin mayor. Como cada
6.2. ORDENAMIENTOS DE INTERCAMBIO iteracin coloca un nuevo elemento en su posicin correcta, un archivo den elemen-
tos requiere de no ms de n - 1 iteraciones. 1
Ordenamiento de burbuja El conjunto completo de iteraciones es el siguiente: '
El primer ordenamiento que presentamos es quiz el ms ampliamente conoci- iteracin O (archivo original) 25 57 48 37 12 92 86 33
do entre los estudiantes que se inician en la programacin: el ordenamiento de bur- iteracin 1 25 48 37 12 57 86 33 92
buja. Una de las caractersticas de este ordenamiento es que es fcil de entender y iteracin 2 25 37 12 48 57 33 86 92
programar. Aunque, entre tod0s los ordenamientos que consideraremos, es iteracin 3 25 12 37 48 33 57 86 92
probable que sea el menos eficiente. iteracin 4 12 25 37 33 48 57 86 92
iteracin 5 12 25 33 37 48 57 86 92
338 Estructuras de datos en C
Ordenamiento 339
iteracin 6 12 25 33 37 48 57 86 92 Qu se puede decir acerca de la eficiencia del ordenamiento de burbuja? En el
iteracin 7 12 25 33 37 48 57 86 92 caso del ordenamiento que no incluye las dos mejoras esbozadas antes, el anlisis es
simple. Hay n - l pasos y n - 1 comparaciones en cada uno de ellos. As, el nme-
Sobre la base de la discusin anterior podramos proceder a codificar el orde- ro total de comparaciones es (n 1) * (n - 1) = n 2 - 2n + 1, que es O(n 2). Por
namiento de burbuja. Sin embargo, existen modificaciones obvias para perfeccionar supuesto, el nmero de intercambios depende del orden original del archivo. Sin
el mtodo anterior. Primero, como los elementos en posiciones mayores o iguales a mbargo, el nmero de intercambios no puede ser mayor que el nmero de compara-
n - i estn en la posicin adecuada despus de la iteracin i, no deben ser consides ciones. Es probable que sea el nmero de intercambios y no el de comparaciones el
rados en las iteraciones siguientes. As, en el primer paso se hacen n - 1 compara- que ms tiempo consuma en la ejecucin del programa.
ciones, en el segundo n - 2 y en el (n - 1)-smo slo una comparacin (entre x[OJ Veamos cmo afectan las mejoras introducidas la velocidad del ordenamiento
y x[l]). En consecuencia, el proceso se agiliza a medida que avanza a lo largo de de burbuja. El nmero de comparaciones en la iteracin i es n - i. As, si hay k ite-
pasos sucesivos. raciones el nmero total de comparaciones ser (n - 1) + (n - 2) + (n 3) + ...
Hemos mostrado que son suficientes n - l pasos para ordenar un archivo de + (n - k), que es igual a (2kn - k 2 -k)/2. Se puede mostrar que el nmero pro-
tamao n. Sin embargo, en el ejemplo precedente el archivo muestra de ocho ele- medio de iteraciones, k, es O(n), de manera que la frmula completa sigue siendo
mentos fue ordenado tras cinco iteraciones, haciendo innecesarias las dos ltimas. O(n 2), aunque el multipliqdor constante es menor que antes. Sin embargo, hay una
Para eliminar pasos innecesarios debemos ser capaces de detectar el hecho de que el. sobrecarga adicional ocasionada por la prueba y la inicializacin de la variable
archivo ya est ordenado. Pero eso es una tarea sencilla, dado que en un archivo switched (una por paso) y la asignacin a la misma del valor TRUE (una vez por in-
ordenado no se hacen intercambios en ningn paso. Llevando el registro de si fueron tercambio).
hechos o no intercambios en un paso dado, puede determinarse si son necesarios pa- La nica caracterstica redentora del ordenamiento de burbuja, es que requiere
sos futuros. En este mtodo, el paso final no hace intercambios si el archivo puede de poco espacio adicional (un registro adicional para guardar el valor temporal para
ordenarse en menos de n - l pasos. el ntercambioy algunas variables enteras simples) y que es O(n) en el caso de un
Usando estas mejoras, presentamos una rutina bubble (burbuja) que acepta archivo ordenado en su totalidad (o casi ordenado en su totalidad). Esto se concluye
dos variables x y n. x es un arreglo de nmeros y n un entero que representa el nme- de observar que slo es necesario un paso den I comparaciones (y ningn inter-
ro de elementos que deben ser ordenados (n puede ser menor que el nmero de ele- cambio), para establecer que un archivo ordenado lo est.
mentos de x). Hay otras maneras de perfeccionar el ordenamiento de burbuja. Una de ellas
es observar que el nmero de pasos necesarios para ordenar un archivo es la distan-
cia ms larga a la que un nmero debe moverse "hacia abajo" en el arreglo. En
bubble (x, n) nuestro ejemplo, el nmero 33, que comienza en la posicin 7 del arreglo encuentra
intx[J,n;
al final la posicin 2, despus de cinco iteraciones. El ordenamiento de burbuja
{
int hold, j, pass; puede acelerarse haciendo que pasos sucesivos tomen direcciones opuestas de mane-
int switched TRUE; ra que los elementos menores se muevan con rapidez al frente del archivo en la mis-
ma forma que lo hacen los mayores haca la parte posterior. Esto reduce el nmero
for (pass= O; pass< n-1 && switched == TRUE; pass++) requerido de pasos. Esta versin se deja como ejercicio al lector.
I* El ciclo externo -controla el nmero de pasos
switched = FALSE; !* Alprincipionosehan
I* realizado intercambios en este paso Qucksort
for (j = o; j < n-pass-1; j++)
!* El ciclo interno maneja los pasos individuales El siguiente ordenamiento que consideramos es el ordenamiento por intercam-
if (X[j) > X[j+1]) {
bio de particin (o quicksorl). Sea x un arreglo y n el nmero de elementos en el
!* se intercambian los elementos
arreglo que debe ser ordenado. Elegir un elemento a de una posicin especfica en el
/* no ordenados en caso de necesidad
switched = TRUE; arreglo (por ejemplo, a puede elegirse como el primer elemento tal que a = x[O]).
hold = x[jl; Suponer que los elementos de x estn separados de manera que a est colocado en la
x[j J = x[j+1l; posicin j y se cumplan las siguientes condiciones:
x[j+1l = hold;
I * fin de if * I l. Cada uno de los elementos en las posiciones de Oaj - l es menor o igual que a.
I* fin de ior *I 2. Cada uno de los elementos en las posiconesj + l a n - 1 es mayor o igual que d.
I * fin de bubble * I

340 Estructuras de datos en C Ordenamiento 341


i f (lb>= ub)
return; I* arreglo ordenado */

Obsrvese que si se cumplen esas dos condiciones para una a y j particulares, a es partiton(x,lb,ub,j); I* Dividirlos elementos del *I
el J-simo menor elemento de x, de manera que a se mantiene en la posicinj cuando el I* subarreglo de tal manera */
arreglo est ordenado en su totalidad. (Demostrar este hecho como ejercicio.) Si se repi- I* que uno de ellos (posiblemente */
te el proceso anterior con los subarreglos que van de x[O] a xU - l] Y de xU + 1] a I* x[lb]) est ahora en xi/] */
x[n _ I] y con todos los subarreglos creados mediante el proceso en iteraciones sucesi- /* (j es un parmetro de salida), y: */
vas, el resultado final ser un archivo ordenado. I* 1. x ( i J <= x ( j] para 1 b <= i < j *!
Ilustremos el quicksort con un ejemplo. Si un arreglo inicial est dado por I* 2. x [ i ] >= x [ j J para j < i <= u b *!
I* x[j] est ahora en su posicin */
25 57 48 37 12 92 86 33 /* final */

quick( x, lb, j - 1) ; / * :Jrdenar el subarreglO en forma */


y el primer elemento (25) se coloca en su posicin correcta, el arreglo resultante es I * recursiva entre las posiciones lb y j ~ 1 */
12 25 57 48 37 92 86 33 quick(x,j + 1,uq); I* Ordenar el subarreglo en forma *I
I* recursiva entre las posiciones j + 1 y ub *I
. En este punto, 25 est en la posicin que le corresponde en el arreglo (x[l]; cada
elemento debajo de esa posicin (12) es menor o igual a 25, y cada elemento arriba de Ahora hay dos problemas. Tenernos que producir un mecanismo para implantar la par-
esa posicin (57, 48, 37, 92, 86 y 33) es mayor o igual que 25. Corno 25 est en su posi- ticin y un mtodo para implantar el proceso completo de manera no recursiva.
cin final, el problema originl se descompuso en el problema de ordenar los dos El objeto de la particin es permitir a un elemento especfico encontrar su posicin
subarreglos correspondiente con respecto a los otros en el subarreglo. Ntese que la manera en que
se ejecuta esta particin es irrelevante para el mtodo de ordenamiento. Todo lo que re-
(12) y (57 48 37 92 86 33) quiere el ordenamiento es que los elementos estn particionados de manera apropiada.
En el ejemplo anterior, los elementos en cada uno de los dos subarchivos se mantienen
No hay que hacer nada para ordenar el primero de esos subarreglos; un archivo de en el mismo orden relativo en.el que aparecen en el archivo original. Sin embargo, un
un elemento ya est ordenado. Para ordenar el segundo subarreglo se repite el proceso Y mtodo de particfonar corno ese es un tanto ineficiente para implantarse.
se vuelve a subdividir el subarreglo. El arreglo completo puede ser visto ahora corno .La manera de efectuar la particin de modo eficiente es la siguiente: Sea a ~ x[lb]
el elemento cuya posicin final se busca. (No se gana eficiencia apreciable seleccionando
12 25 (57 48 37 92 86 33) el primer elemento del subarreglo como aquel que est insertado dentro de su posicin
adecuada; lo nic.o que esto hace es lograr que la escritura de algunos programas sea
donde los parntesis encierran el subarreglo que debe an ser ordenado. La repeticin ms fcil.) Se inicializan dos apuntadores up y down como el lmite superior e inferior
del proceso sobre el subarreglo que va de x[2] a x[7] produce del subarreglo, respectivamente. En cualquier punto de la ejecucin, cada elemento en
una posicin arriba de up es mayor o igual que a y cada elrnento en una posicin deba-
12 25 (48 37 33) 57 (92 86) jo de down es menor o igual que a. Los apuntadores up y down se mueven uno hacia
otro de la siguiente manera.
y las repeticiones posteriores conducen a
Paso l: incrementar en una posicin el apuntador down de manera repetida hasta
12 25 (37 33) 48 57 (92 86) que x[down] > a.
12 25 (33) 37 48 57 (92 86) Paso 2: disminuir el apuntador upen una posicin de manera repetida hasta que
12 25 33 37 48 57 (92 86) x[up] ;;, a.
12 25 33 37 48 57 (86) 92 Paso 3: si up < down, intercambiar x[down] con x[up].
12 25 33 37 48 57 86 92
El proceso se repite hasta que falla la condicin del paso 3 (up < ~ down), momento en
Obsrvese que el ltimo arreglo est ordenado. el cual x[up] se intercambia con x[lb] (que es igual a a), cuya posicin final fue buscada,
En este momento se debe haber notado que el "quicksort" puede ser definido de y se hacej igual a up.
manera conveniente como un procedimiento recursivo. Queremos esbozar un algoritmo
quick(lb, ub) para ordenar todos los elementos de un arreglo x que estn entre las posi-
ciones lb y ub (lb es el lmite inferior del arreglo y ub el superior) como sigue: ordenamiento 343

342 Estructuras de datos en C


Ilustramos este proceso con el archivo muestra, mostrando las posiciones de up y En este punto, 25 est en la posicin adecuada (posicin l ), todo elemento a su izquierda
down cuando son ajustadas. La direccin en la que se examina el archivo est indicada es menor o igual que l y todo elemento a su derecha es mayor o igual que l. Podemos
por medio de una flecha en el apuntador que se est moviendo. Tres asteriscos en una proceder ahora a ordenar los dos subarreglos (12) y (48 37 57 92 86 33) aplicando el mis-
lnea indican que se est realizando un intercambio. mo mtodo.
Este algoritmo particular puede implantarse mediante el siguiente procedi-
a = x[lbl = 25 miento.

abajo--> arriba partition (x, lb, ub, pj)


25 57 48 37 12 92 86 33 int x[l, lb, ub, *pj;
{
abajo arriba int a, down, temp, up;
25 57 48 37 12 92 86 33
a= x[lbl; I* a es el elemento cuya posicin *!
abajo <-- arriba I* final se b'i.l.sca *I
25 57 48 37 12 92 86 33 up = ub;
down = lb;
abajo <--arriba while (down ~ up)
25 57 48 37 12 92 86 33 while (x[downl <= a && down < ub)
down++; I* recorrer el lmite superior del arreglo *!
abajd <--arrib while (x[upl > a)
25 57 48 37 12 92 86 33 up--; I * recorrer el limite superior del arreglo */
if (down < up)
abajo arriba
I* intercambiar x[down] y x[up] *I
25 57 48 37 12 92 86 33
temp = x(downl;
x[downl = x[upl;
abajo arriba
86 33
x[upl = temp;
25 12 48 37 57 92
I* fin deif *I
abajo...:.-> arriba
I* fin de while *I
86 33
x[lbl = x[upl;
25 12 48 '37 57 92
X [ Up] = a
*pj up;
aba)o arriba
92 86 33
I* fin de-partition *I
25 12 48 37 57

abai.? <--arriba Obsrvese que si k es igual a ub - lb + 1 de tal manera que estemos rearreglando
25 12 48 37 . 57 92 86 33 un subarreglo de tamao k, la rutina usa k comparaciones de llaves (de x[down] con a y
x[up] con a) para ejecutar la particin.
down <--arriba La rutina se puede hacer un tanto ms eficiente eliminando algunas de las verifica-
25 12 48 37 57 92 86 33
ciones redundantes. Se deja como ejercicio hacerlo.
<--arriba, abajO ' Aunque el algoritmo recursivo para el quicksort es relativamente claro en trminos
12 48 37 57 92 86 33 de qu lleva a cabo y cmo lo hace, es deseable evitar la sobrecarga de llamadas a rutinas
25
en programas como los de ordenamiento, en los cuales la eficiencia de la ejecucin es
arriba ahajo una consideracin significativa. L<)S llamadas recursivas a qilick pueden eliminarse en
25 12 48 37 57 92 86 33 forma fcil usando una pila comden laseccin 3.4. Una vez que ha sido ejecutadaparli-
tion, ya no se necesitan los parmetros actuales de quick, excepto para calcular los argu-
arriba abajo; mentos de las dos llamadas recursivas siguientes. As, en lugar de poner en la pila los pa-
12 25 48 37 . 57 92 86 33 rmetros actuales en cada llamada recursiva, podemos calcular y poner en la pila los
nuevos parmetros para cada una de las dos llamadas recursivas. Bajo este enfoque,
la pila contiene en cada punto los lmites superior e inferior de todos los subarreglos

344 Estructuras de datos en C. Ordenamiento 345


que an tienen que ser ordenados. Adems, como la segunda llamada recursiva pre- newbnds.lb = j+1;
cede de inmediato al regreso al programa de llamada (como en el problema de las push(&stack, &newbnds);
Torres de Hanoi) puede ser eliminada por completo y remplazada por un salto. Por I* procesar al subarreglo inferior *I
newbnds.lb = i;
ltimo, como el orden en que se hacen las dos llamadas recursivas no afecta la
.newnbds~ub = j-1;
correccin del algoritmo, elegimos en cada caso poner en la pila el subarreglo mayor !* fin deif *I
y procesar de inmediato el ms corto. Como explicamos, esta tcnica mantiene el I * fin de while * I
tamao de la pila al mnimo. ! * fin de while * I
Ahora podemos codificar un programa para implantar el quicksort. Como en el I* fin de quicksort * I
caso de bu/Jble, los parmetros son el arreglo x y el nmero de elementos de x que desea-
mos ordenar, n. La rutina push pone en la pila a lb y ub, popsub los elimina de la Para mayor eficiencia, las rutinas empty, par/ilion, popsub y push deben insertar-
misma y emply determina si la pila est vaca. se. en lnea. Seguir la accin de quicksorl sobre el archivo muestra.
Ntese que elegimos usar x[lb] como el elemento alrededor del cual particionar
#define MAXSTACK ... I * tamao mximo de la pila *I cada subarchivo por conveniencia en el procedimiento parlicin, pero cualquier otro
quicksort(x, n) elemento podra haber sido escogido. El elemento alrede.dor del cual realizamos la parti-
int x[J, n; cin se llama pivol. Incluso no es necesario que pivot sea un elemento del subarchivo;
{
int i, j; particin puede escribir.se con el encabezamiento
struct bndtype
int lb; partition(lb, ub, x, j, pivot)
int ub ;.
newbnds; para partcionar los elementos desde x[lb] hasta x[ub] de manera que todos los elemen-
I* La pila es utilizada por las funciones pop, push y empty tos entre x[/b] y xU - l] sean menores que pivol y todos los elementos entre xU] y
struct { x[ub] mayores o iguales, a pivol. En ese caso, el propio elemento xU] est incluido en
el segundo subarchvo (dado que no est necesariamente incluido en la posicin que le
int top;
struct bndtype bounds[MAYSTACKJ; corresponde), de modo que la segunda llamada recursiva a quick es quick(x, j, ub)
stack; en lugar de quick(x, j + 1, ub).
stack.top = -1; Han sido encontradas varias maneras de elegir el valor de pivol para mejorar la
newbnds.lb = o eficiencia del quicksort garantizando subarchivos ms balanceados. La primer tcni-
newbnds.ub = n-1 ca que se usa es la mediana del primero, ltimo y elemento medio del subarchivo que
push (&stack, &newbnds); debe ser ordenado (es decir, la mediana de x[lb], x[ub] y x[lb + ub)/2]) como valor
I* Repetir mientras exista algn subarreglo. *I del pivot. Esta mediana de tres valores ~st ms cerca de la. mediana d.el subarchivo
I* desordenado en la pila ""/ que est siendo particionado que x[lb], de manera que los dos subarchivos resultan-
while ( !empty(&stack)) { tes de la particin se asemejan ms en cuanto a tamao. En este mtodo el valor de
popsub(&stack, &newbnds);
pivotes un elemento del archivo, de manera que quick (x,j + I, ub) puede usarse
while ( newbnd"S. ub > newbnds. lb)
/* procesamiento del siguiente subarreglo */ como la segunda llamada recursiva.
partition(x, newbnds.lb, newbnds.ub, &j); Un segundo mtodo, llamado ordenamiento por promedios utiliza x[/b] o la
/* :,alocar en la pila al subarreglo mayor */ media de tres elementos como pvot cuando se particiona el archivo origina\, pero
if (j-newbnds.lb > newbnds.ub~j) { aade el cdigo en partition para calcular el trmino medio (los promedios) de los
/* colocar en la pila el subarreglo inferior * dos subarchivos que se estn creando. En las particiones siguientes, se usan como un
i = newbnds.ub; valor de pivot los promedios de cada subarchivo que fueron calculados cuando se
newbnds.ub = j-1; crearon los mismos. Una vez ms, este promedio est ms cerca de la media dehub-
push(&stack, &newbnds); archivo que x[lb] y da como resultado archivos ms balanceados. El'promedio no es
I * procesar el subarreglo Superior * I por fuerza un elemento del archivo, de manera que quick (x, j, ub) tine que usarse
newbnds.lb j+1;
como la segunda llamada recursiva. El cdigo para encontrar el promedio no re-
newbnds.ub = i;
quiere de comparaciones de claves adicionales pern agrega alguna sobrecarga extra.
else { Otra tcnica, llamada Bsort, usa como pvot el elemento medio de un subarchi-
I * colocar en la ppa a.l subarreglo superior *I vo. Durante la particin se intercambian x[up] con x[up + l] si x[up] > x[up + l],
i = newbnds.lb;
Estructuras de datos en C Ordenamiento 347
346
cuando se disminuye el valor del apuntador up. Siempre que aumente el valor del adecuada en el medio del subarreglo. Supngase que no se cumplen las condiciones
apuntador down, se intercambia x[down] con x[down - 1] s x[down] < anteriores y que el arreglo original est ordenado (o casi ordenado). Si, por ejemplo,
x[down -1 l]. Siempre que se intercambien x[up] y x[down] se intercambian x[up] x[/b] est en la posicin correcta, el archivo original se divide en dos partes de tama-
con x[up + 1] s x[up] > x[up + 1] yx[down] se intercambia con x[down - 1] s o O y n 1. Si este proceso contina, se ordena un total de n - 1 subarchivos, el
x[down] < x[down - !]. Esto garantiza que x[up] sea siempre el elemento menor primero de tamao n, el segundo de tamao n - I, el tercero de tamao n - 2 y as
en el subarchivo derecho (de x[up] a x[ub] y que x[down] sea siempre el elemento sucesivamente. Suponiendo k comparaciones para redisponer un archivo de tamao
mayor en el subarchivo izquierdo (de x[/b] a x[down]). k, el nmero total de comparaciones para ordenar el archivo completo es
Esto permite dos optimizaciones: Si no se requirieron intercambios entre x[up]
y x[up + 1] durante la particin, se sabe que el subarchivo derecho estaba ordenado n + (n - l) + (n - 2) + + 2
y no necesita ser puesto en la pila y si no se requirieron intercambios entre x[down] y
x[down - 1] es el subarchivo izquierdo el que estaba ordenado y no necesita ser
puesto en la pila. Lo anterior es similar a la tcnica de mantener una etiqueta en el que es O(n 2). De manera similar, si se ordena el archivo original en orden descen-
ordenamiento de burbuja para detectar que no ocurrieron intercambios durante un dente la posicin final de x[lb] es ub y se vuelve a dividir el archivo en dos subarchi-
paso completo y que no son necesarios pasos adicionales: Segundo, se sabe que vos muy desbalanceados (tamao n - 1 y 0). As, el quicksort no modificado tiene
un subarchivo de tamao 2 est ordenado y no tiene que ser puesto en la pila. Un la propiedad .al parecer absurda de trabajar mejor para archivos "completamente
subarchivo de tamao 3 puede ser ordenado de manera directa con una sola compa- desordenados" que para archivos completamente ordenados. La situacin es preci-
racin y un posible intercambio (entre los dos primeros elementos en un subarchivo samente la. opuesta pata el ordenamiento de burbuja, la cual trabaja mejor para
izquierdo y entre los dos ltimos en un subarchivo derecho). Ambas optimizaciones archivos ordenados y peor para archivos desordenados.
en el Bsort reducen el nmero de subarchvos que deben procesarse. Es posible acelerar el quicksort para archivos ordenados eligiendo un elemento
aleatorio para cada subarchivo como valor del pivot. Si se.sabe que un archivo est
Eficiencia del quicksort casi ordenado, esto podra ser una buena estrategia (aunque, en ese caso, escoger el
elemento medio como pivot sera an mejor). Sin embargo, si no se sabe nada acerca
Cun eficiente es el quicksort? Supngase que el tamao del archivo, n, es del archivo, una estrategia de ese tipo no modifica el comportamiento del peor caso,
una potencia de 2 o sean = 2m, de manera que m = log 2 n. Supngase tambin que dado que es posible (aunque improbable) que el elemento aleatorio escogido cada
la posicin adecuada del pivot vienen a ser siempre la mitad exacta del subarreglo. vez sea el elemento menor de cada subarchivo. Como cosa prctica, son ms comu-
En ese caso se harn ms o menos n comparaciones en el primer paso (en realidad nes los archivos ordenados que un buen generador de nmeros aleatorios que de
n 2- l), despus del cual el archivo estar dividido en dos subarchivos cada uno de ta- casualidad elija en forma repetida el elemento .menor.
El anlisis para el caso en el cual el tamao del archivo no es una potencia de 2
mao n/2, aproximadamente. Para cada uno de esos dos subarchivos se harn n/2
comparaciones y se formarn un total de cuatro archivos cada uno de tamao n/4. es similar pero un poco ms complejo; los resultados, sin embargo, permanecen
Cada uno de esos archivos requerir de n/4 comparaciones produciendo un total de iguales. No. obstante, se puede mostrar que, en promedio (sobre todos los archivos
n/8 subarchivos. Despus de dividir los subarchivos m veces, hay n archivos de ta- de tamao n), el quicksort hace ms o menos 1.386 n log 2 n comparaciones, incluso
mao l. As, el nmero total de comparaciones para el ordenamiento completo ser en su versin no modificada. En situaciones prcticas, el quicksort es con frecuencia
muy prximo a: el ordenamiento disponible ms rpido a causa de su pequea sobrecarga y su com-
portamiento O(n lag n) promedio.
Si se usa la tcnica de la mediana de tres elementos, el quicksort puede ser O(n
n + 2 * (n/2) + 4 * (n/4) + 8 * (n/8) + + n * (nin)
log n) aun si el archivo est ordenado (suponiendo que la partition deja los subarchi-
o vos ordenados). STh embargo, hay archivos patolgicos .en los cuales los elementos
medio, primero y ltimo de cada subarchivo son siempre los .tres menores o mayo-
n + n + n + _n + + n (m terms)
res. En tales casos, el quicksort sigue siendo O(n 2). Por fortuna dichos casos son
comparaciones. Hay m trminos porque el archivo se subdivide m veces. As, el n- raros.
El ordenamiento por promedios es O(n lag n) siempre que los elementos del
mero total de comparaciones es O(n * m) o O(n log n) recurdese que m = log 2 n).
De esta manera, si el archivo satisface las propiedades descritas, el quicksort es O(n archivo estn.distribuidos de manera uniforme entre el mayor y el menor. Una vez
ms, distribuciones raras pueden hacerlo O(n 2) pero esto es menos probable que el
log n), lo que es de relativa eficiencia.
peor de los otros mtodos. Para archivos aleatorios el ordenamiento por promedios
Para el quicksort no modificado en el cual se usa como valor del pivot x[/b]
este anlisis supone que el arreglo original y todos los subarreglos resultantes estn de- no ofrece ninguna reduccin considerable en los intercambios o comparaciones en re-
sorden~dos, de manera que el valor del pivot x[lb] encuentra siempre su posicin

348 Estructuras de datos en C Ordenamiento 349


!acin con el quicksort estndar. Su sobrecarga, al calcular el promedio, demanda
mucho ms tiempo de CPU que el quicksort estndar. Para un archivo que se sabe
est casi ordenado, el ordenamiento por promedios proporciona una reduccin sig- EJERCICIOS
nificativa de los intercambios y comparaciones. Sin embargo, la sobrecarga en el
clculo del promedio lo hace ms lento que el quicksort, a menos que el archivo est 6.2. 1. Pruebe que el nmero de pasos necesario en el ordenamien10 por burbuja del texto,
ordenado casi por completo. antes de que el archivo est ordenado (sin incluir el ltimo paso, que detecta el hecho
El Bsort requiere mucho menos tiempo que el quicksort o el ordenamiento por de que el archivo est ordenado), es igual a la distancia ms larga que tiene que ser
promedios sobre archivos de entrada ordenados o casi ordenados, aunque requiere movido un elemento de un ndice mayor a uno menor.
de ms comparaciones e intercambios que el ordenamiento por promedios para 6.2. l. Vuelva a escribir la rutina bubble de manera que pasos sucesivos avancen en direc-
entradas casi ordenadas (pero el ordenamiento por promedios tiene una sobrecarga ciones opuestas.
significativa para encontrar el promedio). Se requiere de menos comparaciones pero 6.2.3. Pruebe que, en el ordenamiento del ejercicio previo, si dos elementos no son inter-
ms intercambios que el ordenamiento por promedios y ms intercambios y compa- cambiados durante dos pasos consecutivos en direcciones opuestas, ellos estn en su
raciones que el quicksort para entradas ordenadas de manera aleatoria. Sin embargo posicin final.
sus requerimientos de CPU son mucho menores que los del ordenamiento por pro- 6.2.4. Un ordenamiento por conteo se ejecuta comci sigue. Declrese un at:reg:Io count y a
medios, aunque algo mayores que los del quicksort para entradas aleatorias. count[i] igual al nmero de elementoS que sean menores que x[i]. Despus colquese
As, el Bsort se puede recomendar si se sabe que la entrada est casi ordenada o x[i] en la posicin count[i] de un arreglo de salida. (Cudese la. posiblidad de elemen-
si estamos dispuestos a aceptar incrementos moderados en el tiempo de ordenamien- tos iguales.) Escribir una rutina para ordenar un arreglo x de tamao n usando este
to promedio para evitar incrementos muy grandes en el tiempo de ordenamiento del mtodo.
peor caso, Se puede recomendar el ordenamiento por promedios slo en caso de 6.2.5. Suponga que un archivo contfone ~nterOs enfre a y b con muchos nmeros repetidos
entradas que se sabe estn casi ordenadas y el quicksort estndar para entradas alea- varias veces. Un ordenamiento por distribucin procede como sigue. Declare un
torias probables o si el tiempo de ordenamiento promedio tiene que ser tan rpido arreglo number de tamao b a + 1, y a numbrr[ - a] igual al nmero de veces
como sea posible. En la seccin 6.5 presentamos una tcnica que es ms rpida que que aparece el entero i en el archivo_; despus vulvase a poner los valores en el archi- -
vo de acuerdo a lo anterior. Escribir una rutina para ordenar Un arreglo x de tamao
el Bsort Y el ordenamiento por promedios sobre archivos que estn casi ordenados.
n que contenga enteros entre a y b mediante este mtodo.
Los requerimientos de espacio para el quicksort dependen del nmero de
6.2.6. El ordenamiento por tralsposicin par-impar procede de Iii siguiente manera. Pase a
llamadas recursivas anidadas o del tamao de la pila. Es claro que la pila no puede
travs del archivo varias veces. En el primer paSo, compare x[iJ con x[i + 1] para
crecer nunca ms all del nmero de elementos que hay en el archivo original. El cre- todo i impar. En el segundo paso comparar x[i] con x[i + !] para todo i par. Cada
cimiento de la pila por debajo den depende del nmero de subarchivos generados y vez que x{iJ > x[i + l] intercambiarlas. Continuar alternando en esta-forma hasta
de sus tamaos. El tamao de la pila se contiene un poco si siempre se pone en la que el archivo est ordenado.
misma el subarreglo ms grande y se aplica la rutina al ms pequeo de los dos. Esto a. Cul es la condicin para culminar el ordenamiento?
garantiza que todos los subarreglos que resultaron ms pequeos sean subdivididos b. Escriba una rutina en C para implantar el ordenamiento.
primero que los que resultaron ms grandes dando el efecto neto de tener menos ele- c. Cul es, en promedio, la eficiencia de este orden~miento?
mentos en la. pila en cualquier momento dado. La razn para esto es que un 6.2. 7. Vuelva a escribir el programa para el quicks_ort comenzando con el algoritmo recursi-
subarreglo ms pequeo, ser dividido menos veces que uno ms grande. Por su- vo y aplicando los mtodos del captulo 3 para producir una versin no recursiva.
puesto, el subarreglo ms largo ser procesado y subdividido al final, pero esto 6.2.8. Modifique el programa para el quicksort del texto de manera que si un subarreglo es
ocurrir despus que los subarreglos pequeos hayan sido ordenados y en conse- pequeo, se use el ordenamiento de burbuja. Determine en forma emprica con la
cuencia eliminados de la pila. computadora cun pequeo debera ser el subarreglo, de manera que esta estrategia
Otra ventaja del quicksort es la localidad de referencia. Es decir, en un corto mixta sea ms eficiente que el, quicksort ordinario.
periodo de tiempo todos los accesos en el arreglo se hacen en una de dos porciones 6.2,9, Modifique partition de manera que el valor medio de x[/b] x[ub] y x[ind] (donde ind
un tanto pequeas (un subarchivo o una porcin del mismo). Esto asegura eficiencia = (ub + /b)/2) se use para particionar el arreglo. En cules casos est el quicksort
en el ambiente de la memoria virtual, donde pginas de datos se intercambian de ma- usando este mtodo ms eficiente que la versin del textO? En- cules es menos efi-
nera constante entre la memoria interna y la externa. La localidad de referencia ciente?
resul.ta en menos requerimientos de cambios de pginas para un programa particu- 6.2. IO. Implante la tcnica del ordenamiento por promedios. partWon debe usar el promedio
lar. Un estudio de simulacin mostr que en tal ambiente, el quicksort usa menos del subarchivo que se est particionado, calculado cuando fue creado el subarchivo,
recursos de espacio y tiempo que cualquier otro ordenamiento considerado. como valor de pivot y debe calcular el promedio de cada uno de los dos subarchivos
que crea. Cuando se pon~n en la pila los lmites superior e inferior de un subarchivo,
tambin se dbe poner su media.
6.2.11. Implante !a tcnica de Bsort. El elemento medio de cada archivo debe usarse como
pivot, e! ltimo elemento del subarchivo izquierdo que se est creando debe mante-
350 Estructuras de datos en C

Ordenamiento 351
nerse como el mayor en el subarchivo izquierdo y el primer elemento del subatchivo for ( i = O; i < n i++)
derecho debe mantenerse como el menor en el subarchivo derecho. Se deben usar dos x[ i J = pqmindelete( apq);
bits para registrar cul de los dos subarchivos est ordenado al final de particin. Un
subarchivo ordenado no requiere ser procesado an ms. Si un subarchivo tiene 3 o
menos elementos, se ordena enseguida por medio de un intercambio simple, cuando Algoritmo por seleccin directa
mucho.
6.2. 12. a. Vuelva a escribir las rutinas para el ordenamiento de burbuja y el quicksort como El ordenamiento por seleccin directa, u ordenamiento inverso, implanta la
se presentaron en el texto y para los ordenamientos de los ejercicios de manera que cola de prioridad descendente corno un arreglo desordenado. El arreglo de entrada x
se lleve el registro del nmero real de comparaciones e intercambios hechos. se usa para guardar la cola de prioridad, eliminando as la necesidad de espacio adi-
b. Escriba un generador de. nmeros aleatorios (o use uno existente si la instalacin cional. El ordenamiento por seleccin directa es, en consecuencia, un ordenamiento
lo tiene) que genere enteros entre O y 999. en el lugar. Ms an, corno el arreglo de entradax es el propio arreglo desordenado
c. Usando el generador del inciso b, genere varios archivos de tamao 10, .100 y 1000. que representar la cola de prioridad descendente, la entrada ya est en el formato
Aplique las rutinas de ordenamiento de la parte a .para medir los requerimientos adecuado haciendo innecesaria la fase de preprocesarniento.
de tiempo de cada no de los ordenamientos aplicados a cad0. uno_ de los archivos. Por lo tanto el ordenamiento por seleccin directa consiste en su totalidad de
~. Mida los resultados del indso c y cmprelos con los valores tericos presentados una fase de seleccin en la cual el mayor de los elementos entre los restantes, !arge,
en esta seccin. Coinciden? Si no, explicar. Eri particular, redisponga los archi-
se coloca de manera repetida en su posicin correcta, i, al final del arreglo. Para ha-
vos de manera que estn ordenados por completo y en orden inverso y ver cmo se
cdri:iportan los ordl1.amientos con esas entradas.
cer esto, se intercambian large y x[i]. La cola de prioridad con n elementos al inicio
se reduce en un elemento despus de cada seleccin. Tras n ~ 1 selecciones est or-
denado el arreglo completo. As, el proceso. de seleccin tiene que hacerse den - 1
6.3. ORDENAMIENTOS POR SELECCION Y CON ARBOLES a l en lugar de hasta O. La siguiente funcin en C implanta la seleccin directa:

Un ordenamiento por selecc.in es uno en el.cual se selecciona elementos sucesivos en selectsort(x, n)


orden y se colo~an en sus posiciones correspondientes. Los elementos de la entrada in-t x[J, n;
pueden haber sido reprocesados para hacer posible la seleccin ordenada. Cualquier {
int i, indx, j, large;
ordenamiento por seleccin puede conceptualizarse con el siguiente algoritmo gene-
ral que usa una cola de prioridad descendente (recurdese que pqinsert inserta el ele- for (i n-1; i > O; i--) {
mento mayor de una cola de prioridad y pqmaxdelete lo recupera). !* colocar el nmero mayor.de e~tre ~[O] hasta X[i] *!
I* en large y su ndice en i:.dx *I
A"signar dpq a la cola de prioridad descendente vaca . large = x!DJ;
! * preprocesar ls elementos d81 arreglo de entrada */ indx = O;
! * insertndols en la cola de prioridad */ fo r = 1 ; j <= i ; j ++ )
(j
for ( i = o; i < n i++) (x[jl > large)
if {
pqinsert(dpq, x[il); large xljl;
I* Seleccionar cada elemento sucesivo en orden */ indx = j ;
for ( i = n - 1; i >= O; 1--) !* fin de for ... if *!
xlil = pqmaxdelete(dpq); x[indxl = x[il;
x(il = large;
Este algoritmo es llamado de ordenamiento por seleccin general. !* fin deor *I
Examinarnos ahora varios ordenamientos por seleccin diferentes. Hay dos ! * fin de selectsort *I
caractersticas que distinguen a un ordenamiento por seleccin especfico. Una de El anlisis de la seleccin directa es simple. En el primer paso se efectan
ellas es la estructura de datos usada para implantar la cola de prioridad. La segunda n - 1 comparaciones, en el segundo n 2, y as sucesivamente. En consecuencia
es el mtodo usado para implantar el algoritmo general. Una estructura de datos
hay un total de
particular puede permitir una optimizacin significativa del algoritmo de ordena-
miento por seleccin general. (n - l) + (n - 2) + (n - 3) + + l =n * (n - l)/2
Ntese tambin que el algoritmo general puede modificarse para usar una cola
de prioridad ascendente apq en lugar de dpq. El segundo ciclo que implanta la fase comparaciones, que es O(n 2 ). El nmero de intercambios es siempre n - 1 (a no ser
de seleccin sera modificado de la siguiente manera: que se agregue una verificacin para prevenir el intercambio de un elemento consigo

352 Estructuras de datos en C Ordenamiento 353


mismo). Slo se requiere un poco de memoria adicional (para guardar unas pocas i f (Y < info(q))
variables temporales). El ordenamiento puede ser categorizado, en consecuencia, setleft( q,y);
como O(n 2 ). Aunque es ms rpido que el ordenamiento de burbuja. No hay mejora else
si el archivo de entrada est desordenado u ordenado, dado que la prueba se comple- setright(q,y);
ta sin considerar la composicin del archivo. A pesar de ser fcil de codificar, es !* fin defor *I
improbable que se use el ordenamiento por seleccin directa salvo en los archivos !* Se ha construido el rbol recorrerlo en orden !
para los cuales n es pequeo. ntrav( tree);
Tambin es posible implantar un ordenamiento representando la cola de
prioridad descendente mediante un arreglo ordenado. De manera interesante, esto Para convertir el algoritmo en una rutina que ordene un arreglo es necesario
conduce a un ordenamiento que consta de una fase de preprocesamiento en la que se revisar intrav de tal manera que en la visita a un nodo se coloquen los contenidos del
forma un arreglo ordenado den elementos. La fase de seleccin es, en consecuencia mismo en la posicin siguiente del arreglo original. .
superflua. Este ordenamiento se presenta en la seccin 6.4 como el ordenamiento por En realidad, el rbol de bsqueda binaria representa una cola de prioridad
insercin simple; no es un ordenamiento de seleccin porque no se requiere de esta ascendente, como se describi en los ejercicios 5.1.13 y 5.2.13. La construccin del
ltima. rbol representa la fase de preprocesamiento y el recorrido la fase de seleccin del al-
goritmo de ordenamiento por seleccin general.
Por lo regular, la extraccin del elemento mnimo (pqmindelete) de una cola de
Ordenamiento por rboles binarios prioridad, representada mediante un rbol de bsqueda binaria, implica descender
por la parte izquierda del rbol desde la raz. En realidad, ese es el primer paso del
En el resto de esta seccin ilustramos algunos mtodos de ordenamiento por proceso en un recorrido en orden. Sin embargo, como una vez construido el rbol no
seleccin que representan una cola de prioridades mediante un rbol binario. El pri- se insertan en el mismo nuevos elementos y el elemento mnimo no tiene que ser eli-
mer mtodo es el ordenamiento con rbol binario de la seccin 5.1 que usa un rbol minado en realidad, el recorrido en orden implica de manera eficiente el proceso de
de bsqueda binaria. Se aconseja al lector revisar dicha seccin antes de proseguir. seleccin sucesiva.
El mtodo implica el examen de la cada elemento del archivo de entrada y el La eficiencia relativa de este mtodo depende del orden original de los. datos. Si
colocar al mismo en la posicin apropiada dentro de un rbol binario. Para en- el arreglo original est ordenado por completo (u ordenado en orden inverso), el
contrar la posicin adecuada de un elemento y, se toma en cada nodo una rama iz- rbol resultante aparece como una secuencia slo de ligas derechas (o izquierdas),
quierda o derecha dependiendo de si y es menor que el elemento que est en el nodo como en la figura 6.'. L En ese caso la insercin. del primer nodo no requiere compa-
o mayor o igual a l. Una vez que cada elemento de entrada est en su posicin ade- racin alguna, el segundo nodo requiere de dos, el tercero de tres, y as sucesivamen:
cuada en el rbol, se puede recuperar el archivo ordenado mediante un recorrido en te. As, el nmero total de comparaciones es
orden del rbol. Presentamos un algoritmo para este ordenamiento, modificndolo
para acomodar la entrada como un arreglo preexistente. Convertir el algoritmo a 2+ 3+ + n =n (n + 1)/2 - l
una rutina en C es sencillo.
que es O(n 2).
Por otra parte, si en el arreglo original los datos estn organizados de tal mane-
I* establecer el primer elemento como raz *I ra que la mitad de los nmeros anteriores a uno dado, a, en el arreglo sean menores
tree maketree(x[D]); que a y la otra mitad mayores que a, resultan rboles balanceados como los de la
I* repetir para cada elemento sucesivo *I figura 6.3.2. En tal caso la profundidad del rbol binario resultante es el menor ente-
for (i = 1; i < n; i++) {
ro d, mayor o igual que log 2 (n + l) - l. El nmero de nodos en cualquier nivel/
y x[ i l;
tree; (con posible excepcin del ltimo) es 2 1 y el nmero de comparadones .necesarias
q -
p - q;
para colocar un nodo en el nivel/ (excepto cuando/ = O) es 1 + l. As, el nmero to-
/* recorra el rbol hacia abajo hasta alcanzar una hoja *I tal de comparaciones est entre
while (P !- null) {
d-1 d
q - p;
i f (Y< info(p))
p left(p);
d + I
.,,,]
z1 u+ 1) y zi*U+l)
/=!
else
p right(p);
Se puede mostrar (los lectores inclinados hacia las matemticas podran estar intere-
I * fin de while *I sados en probar este hecho como ejercicio) que las sumas resultantes son O(n lag n).

354 Estructuras de datos en C Ordenamiento 355


Datos originales: Datos originales: al rbol cuando ste se crea, se reduce el tiempo de recorrido y elimina la necesi-
4 8 12 17 26 26 17 12 8 4
dad de una pila (implcita en la recursin o explcita en un recorrido en orden no
recursivo).
4 Este ordenamiento requiere que sea reservado un nodo en el rbol para cada
elemento del arreglo. Dependiendo del mtodo usado para implantar el rbol, se
puede requerir de espacio para apuntadores al mismo y hebras, si existen. Este
8 requerimiento adicional de espacio, unido a la pobre eficiencia en cuanto a tiempo
O(n 2) para entradas ordenadas o que estn en orden inverso, representa la desventa-
ja primaria del ordenamiento por rbol binario.

Heapsort

Las desventajas del ordenamiento de ilrbol binario pueden remediarse mediante


el heapsort, un ordenamiento en el lugar que requiere de slo O(n log n) operaciones
sin importar el orden d.e la entrada. Definir un heap descendente (tambin llamado
max heap o rbol parcialmente ordenado descendente) de tamao n como un rbol
binario cuasi-completo den nodos tal que, el contenido de cada nodo sea menor o
Nmero de comparaciones: 14 Nmero de comparaciones: 14 igual ue el de su padre. Si se us la representacin secuencial de un rbol binario
cuasi-completo, esta condicin se reduce a la desigualdad
(a) (b)
info[j] $ info[(j- l)/2] for O :a; ((j - 1)/2) < j:a, n - I
Figura 6.3.1
De esta definicin queda claro de un heap descendente que la raz del rbol (o
Por fortuna, se puede rnostra.r que si todo ordenamiento posible de la entrada el primer elemento del arreglo) contiene el elemento mayor en el heap. Ntese tam-
s considera tan probable, resultan c9n mayor frecuencia rboles balanceados que bin que cualquier camino de la raz a una hoja (o de hecho cualquier camino en el
no balanceados. El tiempo promedio de ordena.miento par un ordenamiento de r- rbol que incluya no ms de un nodo en cada nivel) es una lista ordenada en orden
bol binario es por lo tanto O(n log n), aunque la. constante de proporcionalidad es descendente. Tambin es posible definir un heap ascendente (o min heap) como un
mayor en promedio que en el mejor de los cass. Sin embargo, en el peor caso rbol binario cuasi cornpleto tal que los contenidos de cada nodo sean mayores o
0

(entrada ordenada), el ordenamiento de rbol binario es O(n 2). Por supuesto, una iguales que los contenidos de su padre. En un heap ascendente, la raz contiene el
vez que el rbol ha sido creado, se emplea tiempo en recorrerlo. Si se aaden hebras elemento menor del heap y cualquier camino de la raz a una hoja es una lista orde-
nada de manera ascendente.
Un heap permite una implantacin muy eficiente de una cola de prioridad. Re-
Datos originales: Datos originales: cordar de la seccin 4.2 que una lista ordenada que contenga n elementos permite
12 8 17 4 26 17 8 12 4 26 que la insercin en una cola de prioridad (pqinsert) se implanta usando un promedio
de accesos de nodos aproximado a n/2 y la eliminacin del mximo o mnimo
(pqmindelete o pqmaxde/ete) usando slo un nodo de acceso. As, una secuencia de
n inserciones y n eliminaciones de una lista ordenada corno la que necesita un orde-
namiento por seleccin, requiere de O(n 2) operaciones. Aunque la insercin en una
cola de prioridad usando un rbol de bsqueda binaria podra necesitar de slo unos
pocos accesos de nodo corno log 2 n, podra requerir hasta n accesos si el rbol est
desbalanceado. As, un ordenamiento por seleccin usando un rbol de bsqueda
26 binaria podra requedr tambin O(n 2) operaciones, aunque en promedio slo se ne-
cesitan O(n log n).
Nmero de comparadones: 10 Nmero de comparaciones: 1O
Corno veremos, un heap permite que tanto la insercin corno la eliminacin se
implanten en O(n log n) operaciones. As, un ordenamiento por seleccin que con-
( a) (b) Figura 6.3.2 sista de n inserciones y n eliminaciones puede implantarse usando un heap en O(n

35.6 Estructuras de datos en C Ordenamiento 357


incluido si y slo si 2 * i + 1 < = m y dpq[2 * i + 2] est incluido si y slo si 2 * i +
2 = m. Si 111 es menor que p, subtree(p, m) se define como el rbol vaco.
Para implantar pqmaxdelete(dpq, k), notamos que el mximo de los elemen-
log n) operaciones, aun en el peor de los casos. Una ganancia adicional es que el pro- tos, est siempre en la raiz de un heap descendente de k-elementos. Cuando se elimi-
pio heap se puede implantar dentro del arreglo de entrada x, usando la implantacin na dicho elemento, los k - 1 elementos restantes tienen que ser redistribuidos de las
secuencial de un rbol binario cuasi-completo. El nico espacio adicional requerido posiciones 1 a k - 1 a las posiciones O a k - 2 de manera que el segmento del
es para variables del programa. El heapsort es, por lo tanto, un ordenamiento O(n arreglo resultante de dpq[O] a dpq[k - 2] siga siendo un heap descendente. Sea
log n) en el lugar. adjust-heap(root, k) la operacin de reacomodar los elementos dpq[root + 1] a
dpq[k] dentro de dpq[root] hasta dpq[k - 1] de manera que subtree(root, k - 1)
El heap como una cola de prioridad forme un heap descendente. Entonces pqmaxdelete(dpq, k) para un heap descen-
dente de k-elementos puede implantarse mediante:
Implantemos ahora una cola de prioridad descendente usando un heap descen-
dente. Suponer que dpq es un arreglo que representa de manera implcita un heap p=dpq[Dl;
descendente de tamao k. Como la cola de prioridad est contenida en los elementos adjustheap(O,k - 1);
O a k - 1 del arreglo, agregamos k como un parmetro de las operaciones de inser- return(p)';
cin y eliminacin. Entonces la operacin pqinsert(dpq, k, elt) puede implantarse
insertando simplemente elt dentro de su posicin correcta en la lista descendente for- En un heap descendente, no slo la raz es el elemento mayor del arbol, sino
mada por el camino que va de la raz del heap(dpq[O]) a la hoja dpq[k]. Una vez eje- que un elemento en cualquier posicin p tiene que ser el elemento mayor de
cutada pqinsert(dpq, k, elt), dpq se convierte en un heap de tamao k + l. subtree(p, k). Ahora, subtree(p, k) consta de tres grupos de elementos; su raz,
La insercin se hace. recorriendo el camino desde la posicin vaca k hasta la dpq[p]; su subrbol izquierdo, subtree(2 p + 1, k); y su subrbol derecho,
posicin O(la raz), buscando 'el primer elemento mayor o igual que elt. Cuando ha subtree(2 *P + 2, k). dpq[2 p + 1], el hijo izquierdo de la raz, es el elemento ma-
sid encontrado dicho elemento, se inserta elt precedindolo de inmediato en la tra- yor del subrbol izquierdo y dpq[2 p + 2L el hijo derecho de la raz, es el elemento
yectoria (es decir elt se inserta.como su hijo). Como cada elemento menor que elt se mayor del subrbol derecho. Cuando la raz dpq[p] se elimina, se mueve hacia arri-
pasa durante el recorrido, se desva un nivel hacia abajo en el. rbol para hacer espa- ba el elemento ms grande de esos dos para tomar su lugar como el nuevo elemento
cio a elt. (Este recorrimiento es necesario porque estamos usando la representacin mayor de subtree(p, k). Entonces, el subrbol con raz en la posicin del elemen-
secuencial en lugar de la representacin ligada del rbol. Un nuevo elemento no to mayor movido hacia arriba tiene que ser reajustado a su vez.
puede ser insertado entre dos elementos existentes sin recorrer algunos.) Definamos largeson(p, m) como el hijo mayor de dpq[p] dentro desubtree(p,
Esta operacin de insercin en el heap se llama tambin operacin siftup dado m). Puede implantarse como:
que elt examina su camino hacia arriba en el rbol. El lgoritmo siguiente impl11nta
pqinsert (dpq, k, elt): S=2*p+1;
if (s + 1 <= m && x[sl < x[s + 11)
s = s + 1;
s = k; I * verificar si se est fuera de lmites * /
f = ( s - 1) / 2; / * fes el padre de; *f
if(s>m)
while (s > O && dpq[f] < elt { return (-1);
dpq[sl = dpq[fl; else
s = f; /* avanzar hacic! arriba por el rbql * /
return ( s);
f=(s-1)/2;
} /* fin de while *I
Entonces, adjustheap(root, k) puede implantarse de manera recursiva por medio de:
dpq[sl = elt;
f = root;
La insercin es, de manera clara, O(log n) dac:!o que un rbol bina.ria cuasi- s = largeson(f, k - 1);
completo con n nodos tiene log 2 n + 1 niveles y se.accesan a lo sumo un nodo por if (s >= O && dpq[kl < dpq[sl)
nivel. . dpq[fl = dpq[sl;
Examinamos ahora como implantar pqmaxdelete(dpq, k) para un heap des- adjustheap(s, k);
cendente de tamao k. Primero definimos subtree(p, m), donde mes mayor que p,
else
como el subrbol (del heap descendente) que tiene raiz en la posicin p con los ele- dpq[ f] dpq[k];
mentos dpq[p] a dpq[m]. Por ejemplo, subtree(3, 10) consta de la raz dpq[3] y sus
dos hijos dpq[?] y dpq[8]. subtree(3, 17) consta de dpq[3], dpq[?], dpq[8], dpq[l5],
dpq[16] y dpq[l7]. Si dpq[] est incluido en sub/ree(p, m), dpq[2 * i + l] est Ordenamiento 359

358 Estructuras de datos en C


A continuacin presentamos una versin iterativa de adjustheap. El algoritmo Las lneas punteadas en esa figura indican un elemento que est recorrido hacia aba-
usa una variable temporal kva!ue para guardar el valor de dpq[k]: jo en el rbol.
La figura 6.3.4 ilustra el ajuste del heap donde x[O] se selecciona de manera
f = root;
repetida, se coloca en la posicin que le corresponde y en el arreglo y se reajusta el
kvalue - dpq[k); heap, hasta que todos los elementos heap son procesados. Notar que despus de
s = largeson(f, k - 1); "eliminar" un elemento del heap, dicho elemento permanece en el arreglo; slo que
while (s >= o && kvalue < dpq[sl) se ignora en el procesamiento subsecuente.
dpg[fl = dgp[sl;
f s; El procedimiento heapsort
s = largeson(f, k - 1);
} Presentamos ahora un procedimiento para heapsort con todos los subprocedi-
dpq[fl = kvalue; mientos (pqinsert, pqmaxdelete, adjustheap y largeson) expandidos en lnea e in-
tegrados para mayor eficiencia:
Obsrvese que recorremos una trayectoria del rbol desd~ la rz hacia una
hoja recorriendo hacia arriba todos los elementos mayores qu dpq[kl una posicin heapsort ( x, n)
e insertando dpq[kl en la posicin correspondiente. De nuevo, el corrimiento es int x[ Ji n;
necesario porque estamos us.ando la represent~cin secuencial en lugar de la implan- {
tacin ligada de.unrbol..El prpcedimiento ele ajuste se llama con frecuencia opera,. int i, elt, s, f, ivalue
cin siftdown porque dpq[k) examina su camino desde la raz hacia abajo en.el I* etapa de procesamiento; crear el heap inicial */
rbol. far (i= 1; i < n; i++) {
Este algoritmo de eliminacin en un heap tambin es O(log n), dado que hay elt=x[il;
log2 n + 1 niveles en el rbol y se accesan a lo sumo dos nodos er C8cda nivel. Sin em-. I* pqinsert(x, i, .elt) *I
bargo, la sobrecarga por corrimiento y clculo de largeson es ~ignificativa. s = i;
f = (s-1)/2;
Ordenamiento usando un lieap while (s > O && x[fl < elt)
x[sJ = x[fJ;
El heapsort no es ms que una implantacin del algoritmo de ordenamiento s = f;
por seleccin general usando el arreglo de entrada. x como un heap que represente f = (s-1)/2;
una cola de prioridad descendente. La fase de reprocesamiento crea un heap de I * fin de while *I
tamao n usando la operacin siftup y la fase de seleccin redistribuye los elementos x[sl = elt;
del heap en el orden en que los elimina de la cola de prioridad usando la operacin !* fin de for *I
siftdown. En ambos casos los ciclos no necesitan incluir el caso en que i es igual a O, I * etapa de seleccin; eliminar de manera repetida a x_[O], *!
dado que x[O) es ya una cola de prioridad de un elemento y el arreglo esta ordenado I * insertarlo en la posicin correcta y ajustar el heap *I
una vez que los elementos de x[l] hasta x[n - 1) estn en las posiciones que les for ( i = n-1; i > O; i--) (
I* pqmaxdelete(x, i_+1) *I
corresponden.
ivalue = x[iJ;
xlil = x[Dl;
/* Crear la cola de prioridad antes de cada interacin en ~l ciclo' f = O;
* la cola de prioridad est formada por los elementos desde x[OJ , I* s = largeson: (O, i-1) */
/* hasta x[ ~11}. Cada iteracin a<jrega a :X[i} a la col. if ( i == 1)
for ( i = 1; i < n; i++) s -1;
pqinsert(x, i, x[i)); else
/* seleccionar cada elemento sucesivo en orden "' / s 1;
for ( i = n - 1; i > O; i--) if ( i > 2 && X [ 2J > X [ 1))
x[il = pqmaxdelete(x, i + 1); s = 2

La figura 6.3.3 ilustra la creacin de un heap de tamao 8 del archivo original. while (s >= O && ivalue < x[sl) .{
x[fl = x[sl;
25 57 48 37 12 92 86 33 f = s;

Estructuras de datos en C Ordenamiento 361


360
I* s = largeson(f, i-1) */
s = 2*f+1;
if (s+1 <= i-1 && x[sl < x[s+1l)
s = s+li
if (S > i-1)
s = -1;
I* fin de while *I
x[f] = ivalue;
!* fin defor *I
I * fin de heapsort *I

0
(a) Arbol origiral. (b) x[7] = pqmaxdelete (x, 8)

(e) x[6J: = pqmaxdelete (x, 7)

j/

(d) x[5]: = pqmaxdelete (x, 6) (e) x[4j: = pqmaxdefete /x, 5)

Figur~ 6.3.4 Ajuste de un heap.


Figura 6.3.3 Creacin de un heap de tamao 8.

362 Estructuras de datos en e Ordenamiento 363


6.3.1. Explique por qu el ordenamiento por seleccin directa es ms eficiente que el ordena-
miento de burbuja.
6.3.2. Considere el siguiente ordenamiento por seleccin cuadrtica: Dividir los n elementos
del archivo en...; n grupos de..j ti elementos cada uno. Encuentre el elemertt6 mayor de
cada grupo e insrtelo dentro de un arreglo auxiliar. Encuentre el elemento mayor
de este arreglo auxiliar. Este es el elemento mayor del archivo. Despus remplace este
elemento en el arreglo por el siguiente elemento ms grande del grupo del cual ven.a el
primero. Encuentre otra vez el elemento ms grande del arreglo auxiliar. Este es el Se-
gundo elemento ms grande del archivo. Repita el proceso hasta que el archivo haya
sido ordenado. Escriba una rutina en C para implantar un ordenamiento por seleccin
cuadrtica de manera tan eficiente como sea posible.
(f)xl4J: =qmaxdelete(x,4)
6.3.3. Un torneo es un rbol estrictamente binario Cuasi-completo en -el cual cada nodo no
hoja contiene el mayor de los dos elementos en ss hijos~ As, los contenidos de las ho"'
x[I J jas de un torneo determinan por C()mpleto los contenidos. de. todos sus. nodos; Un
torneo con n hojas representa un conjunto de n elementos.
a. Desarrolle un algoritmo pqinsert(t, n, elt) para agr~gar un nuevo ele~e~to elt a un
torneo que contenga n hojas representado de manera implcita .por un arr~glo t.
b. Desarrolle un algoritmo pqmaxdelete(t, n) para eliminar el elenerito inximo de un
torneo con n elementos al remplazar la hoja que lo contenga por cualquier valor o
menor que el de cualquier elemento posible (por ejemplo, -1 en un tO.rneo de ente-
ros no negativos) y lug reajustar todos ls valores en el camino de esa
hoja a la raz.
c. Muestre cmo simplificar pqmaxdelet guardando un apuntador a .c~da hoja en
cada campo info de un .nodo .no hoja, en l~gar .~.~~. valor del elemerito: real.
d. Escriba un programa en C para implantar un ordenamiento por seleccin usando
un torneo. La fase de preprocesamiento construye el torneo inicial a partir de un
attglo X y'la fase de seieCciil"aplica en forma repetidapqmaxdelete. Tal mtodo
(g} Xl[31: =- pqmaxdelete (x, 3) (h) x(2J: = pqmaxdefere (x; ,2:'>. El arreglo est ordenado. se conoce corno ordenamiento por torneo (to.umament sort).
e. Cul es la eficiencia del ordenamief!tO por torne~ cop,parada con la del heapsort?
Figura 6.3;4 [cont.)
f. Pruebe que el ordenamiento por torneoes O(n togn)'J)ara toda_entrada.
6.3.4. Defina un rbol ternario cuasi-completo como un ,rbol en el que cualquier nodo tiene
a lo sumo tres hijos y en el cual los nodos pueden ser enumerados d~ Oa n.-:- 1, de ma-
Para analizar el heapsort, obsrvese que un rbol binario completp .. con n
nera que los hijos de node[i] sean node[3 * i + !], node[3 * i + 2] Y node[3 * i + 3].
nodos (donde n es menor en 1 que una potencia de dos) tiene log (n + 1) niveles. Defina un heap temario como un rbol ternario cuasi-completo en el cual el conteni40_
As, s cada elemento en el arreglo fuese una hoja que tuviese que ser filtrada a travs de' cada: nodo es mayor o igual que el contenido de todos sus descendientes. Escriba un~
del rbol completo tanto cuando se crea como cuando se ajusta el heap, el ordena- rutina de ordenamiento similar al heapsort Sando' n heap terna"d~.
miento seguira siendo O(n log n). 1
6.3.5. Escriba una rutina combine(X) que acepte un arregl x eh el cual los sub~rboles con'raii.
En el caso promedio, el heapsort no es tan eficiente como el quicksort. Los ex- en x[l] y x[2] sean heaps y que modifique el arrg!ox de manera que represente un heap
perimentos. indican que el heapsort requiere dos veces ms tiempo que el quicksort nico.
para entradas colocadas de manera aleatoria. Sin embargo, el heap~ortes muy supe- 6.3.6. Reescriba el programa de la seccin 5.3 que implanta el algoritmo .de. Huffman,.cte
rior al quicksort en el peor caso. De hecho, heapsort permanece O[n log n] en el peor manera que el conjunto de nodos raz forme una cola de prioridad implantad median-
de los casos. El heapsort tampoco es muy eficiente paran pequeo por la sobrecarga te un heap ascende"nte.
de la creacin inicial del heap y del clculo de la ubicacin de padres e hijos. 6.3.7. Escriba un 'progtarna en C que use un heapascenderite pra interclar n archivos cte
El requerimiento de espacio para el heapsort (aparte de los ndices del arreglo) entrada, cada uno ordenado de manera ascendente, en un solo archivo de salida. Cada
es slo un registro adicional para guardar la variable temporal para el intercambio, nodo del heap contiene un nmero de archivo y un valor. El valor se usa como llave
si se usa la implantacin con arreglo de un rbol binario cuasi-completo. mediante la cuaJse organiza el heap. Al inicio, se lee un valor de cada archivo Y los n
valores forman un heap ascendente, con el nmero de archivo del que venia cada valor

364 Estructuras de datos en C Ordenmiento 365


y que se ha mantenido junto a ese valor en el nodo. El valor ms pequeo est entonces
en la raz del heap y es la salida, tomando su lugar el siguiente~ valor de su archivo aso-
ciado. Ese valor, junto con su nmero de archivo a.saciado, se intercala en la posicin
que le corresponde en el heap y se saca el nuevo valor raz. Este proceso de salida /en- que es O(n 2). Sin embargo, el ordenamiento por insercin simple es, por lo regular,
trada/intercalacin se repite hasta que no reste ningn valor de entrada. an mejor que el ordenamiento de burbuja. Mientras ms prximo est un archivo a
6.3.8. Desarrolle un algoritmo.usando un h~ap de k elementos para encontrar los k nmeros ser ordenado, ms eficiente se volver la insercin simple. El nmero promedio de
mayores en un gran archivo de n nineros desordenado. comparaciones en el ordenamiento por insercin simple (considerando todas las po-
sibles permutaciones del arreglo de entrada) es tambin O(n 2). Los requerimientos
de espacio para este ordenamiento comprenden slo una variable temporal y.
6.4. ORDENAMIENTOS POR INSERCION La velocidad del ordenamiento se puede mejorar un tanto, usando una bs-
queda binaria (ver las secciones 3.1, 3.2 y 7.1) para encontrar la posicin adecuada
Insercin simple de x[k] en el archivo ordenado x[O], ... , x[k - l]. Esto reduce el nmero total de
comparaciones de O(n 2) a O(n log n). Sin embargo, aun cuando la posicin correcta
Un ordenamiento. por, insercin es uno en que reordena un conjunto de i para x[k] sea encontrada en O(log n) pasos, cada uno de los elementos x[i + l], .. ,
registros insertando registros en un archivo ordenado existente. Un ejemplo de orde- x[k - 1] tiene que ser movido una posicin. Esta ltima operacin, ejecutada n
namiento por insercin simple es el siguiente procedimiento: veces, requiere de O(n 2) remplazamientos. No obstante, la tcnica de bsqueda
binaria no perfecciona, por lo tanto, los requerimientos de tiempo globales para el
insertsort ( x,, n) ordenamiento, de manera significativa.
in t x C J , n; Se puede hacer otra mejora al ordenamiento por insercin simple usando inser'
{ cin en lista. En este mtodo hay un arreglo link de apuntadores, uno para cada uno
int i, k, y; de los elementos del arreglo original. En un principio link[i] = i + 1 para O < =
1 * Al i~icio X[O] puede.pensrse..comO un archivo o~denado de i< n I y /ink[n - 1] = - l. As, el arreglo se puede imaginar como una lista lineal
* un eleme11.tc. Despus de cada repeticin en el siguiente sealada por un apuntador externofirs/ inicializado a O. Para insertar el.k-simo ele-
* ciclo, los elementos desde x[OJ hasta x{k] .estn en Orden mento se recorre la lista ligada hasta encontrar la posicin adecuada a x[k], o hasta
far (k =
1; k<. n; k++) f que se alcance el final de la lista. En ese momento x[k] puede insertarse en la lista
/* inseii.1,. .x[k] en el archivo ordenado __ . *! con slo ajustar los apuntadores a la misma sin recorrer ningn elemento del
y=x[kl; arreglo. Esto reduce el tiempo requerido por la insercin pero no el tiempo necesario
/* baar una posicin todos los elementos mayores que y
far (i = k-1; i >= q && y < x[il; i--) para la bsqueda de la posicin adecuada. Los requerimientos de espacio se incre-
x[i+1l = x[il; mentan tambin a causa de un arreglo link suplementario. El nmero de compara-
! * insertar y en la pOsin correcta *! ciones sigue siendo O(n 2), aunqueel nmero de remplazamieritos en el arreglo link
x[i+1l = y; es O(n). El ordenamiento por insercin en lista pude verse como un ordenamiento
!* fin de for *I por seleccin general en el cual la cola de prioridad est representada por una lista
/* fin de insertsort *I ordenada. Una vez ms, no se necesita seleccin dado que los elementos estn orde-
nados tan pronto como se completa la fase de preprocesaniiento o insercin. Se deja
Como observamos al principiode la seccin 6.3, el ordenamiento por insercin como ejercicio al lector programar los ordenamientos por insercin binaria y por in-
simple puede verse como un ordenamiento por seleccin general en el cual la cola de sercin en lista.
prioridad se implanta como un arreglo ordenado. Slo es necesaria la fase de prepro- Ambos ordenamientos, por insercin simple y por seleccin directa, son ms
cesami.ento en la que se insertan lo.s elementos en la cola de prioridad; una vez que eficientes que el ordenamiento de burbuja. El ordenamiento por seleccin requiere
stos han sido insertados, ya estn ordenados, de manera que la seleccin deja de ser de menos asignaciones que el ordenamiento por insercin pero ms comparaciones.
necesaria. As, se recomienda el ordenamiento por seleccin para archivos pequeos cuando
Si el archivo inicial est ordenado, se hace una sola comparacin en cada paso, los registros son grandes, de manera que la asignacin no sea costosa, pero lasUaves
de manera que el ordenamiento es O(n). Si el archivo est inicialmente ordenado en sean simples, de manera que la comparacin sea barnta. Si se presenta la situacin
orden inverso, el ordenamiento e.s O(n 2), dado que el nmero total de compara- inversa, se recomienda el ordenamiento por insercin. Si la entrada est inicialmente
ciones es en una lista ligada, se recomienda la insercin en lista aun cuando los registros sean
grandes, dado que no se requieren movimientos de datos (de manera opuesta a la
(11 - l) + (11 - 2) + + 3 + 2 + l = (n L) ~ 11/2 modificacin de apuntadores).
Por supuesto, el heapsort y el quicksort son, ambos, ms eficientes que la in-
sercin o seleccin para n grande. El punto de equilibrio es cercano a 20-30 para
366 Estructuras de datos en C el quicksort; para tamaos con menos de 30 elementos, usar ordenamiento por in-

Ordenamiento 367
sercin; para ms de 30 usar quicksort. Una aceleracin til del quicksort usa orde- (x[3])
namiento .por insercin para cualquier subarchivo de tamao menor que 20. Para
(x[4])
el heapsort el punto de equilibrio con el ordenamiento por insercin es aproxima-
do a 60-70. segunda iteracin (incremento = 3)
(x[O], x[3], x[6])
Shell sort
(x[ l], x[4]. x[7])
Se puede lograr un perfeccionamiento ms significativo del ordenamiento por (x[2], x[5])
insercin simple que el que se alcanza con la insercin binaria o en lista usando el
Shell sort (u ordenamiento por disminucin de incremento), nombrado as en honor tercera iteracin (incremento = 1)
a su descubridor. Este mtodo ordena subarchivos separados del archivo original. (x[O], x[l], x[2], x[3]. x[4], x[5], x[6], x[7])
Estos subarchivos contienen todo elemento k-simo del archivo original. El valor de
k se llama un incremento. Por ejemplo, si k es 5, se ordena .primero el subarchivo La figura 6.4.1 ilustra el Shell sort en este archivo muestra. Las lneas debajo
que contiene a x[O], x[S] x[IO]. De esta manera se ordenan cinco subarchivos, y cada de cada arreglo unen elementos individuales de los subarchivos separados. Cada uno
uno contiene la quinta parte de los elementos del archivo original. Estos son (leyen- de los subarchivos se ordena usando el ordenamiento por insercin simple.
do _a trnvs de) Presentamos abajo una rutina para implantar el Shell sort. Dicha rutina re-
quiere adems de los parmetros x y n, un arreglo incrmnts, que contiene bs incre-
Subarchivo l -> x[O] x[5] x[IO] mentos decrecientes del ordenamiento y numinc, el nmero de elementos en el
Subarchivo 2 ~> x[l] x[6] X [ J J]
arreglo incrmnts.
Subarchivo 3 -> x[2] x[7] x[l2] .
Subarchivo 4 -> X [3] x[8] x[l3]
Subarchivo 5 -> . x[4] x[9] x[l4] Ar:hivo 25 17 48 37 12 92 86 33
original
El _isimo elemento_deljsimo subarchivo es x[(i - 1) * 5 + j - I]. Si se elige un
incremento diferente k, se dividen los. k subarchivos de manera que el i-simo ele-
mento delj-simo subarchivo sea x[(i - 1) * k + j - I]. Paso 1 25 57 48 37 12 92 86 33
span = 5
Despus de ordenar los primeros k subarchivos ( en general por i_nsercn
simple), se elige un nuevo valor menor de k y se vuelve a partkionar el archivo en un
nuevo conjunto de subarchivos. Cada uno de esos subarchivos ms grandes se orde-
na y se repite el proceso una vez rris con un valor an ms pequeo de k._AI final, el
valor de k se hace 1, de tal manera que se ordena el subarchivo que contiene.al archi-
vo completo. Al principio de todo el proceso se fija una secJJencia <lec.re.ciente de Paso 2 25 57 33. 37 12 86 48
span = 3
incrementos. El ltimo valor de dicha secuencia tiene que ser L
Por ejemplo, si el archivo original es

25 57 48 37 12 92 86 33

y se elige la secuencia (5, 3, 1), se ordenan los siguientes subarchivos en cada itera- Paso 3 25 12 33 37 48 92 86 57
cin:

'
span = 1 1 1
1 1 1 1 1
primera iteracin (incremento = 5)
Archivo 12 25 33 37 48 57 86 92
(x[O], x[5])
ordenado.
(x[ l], x[6]) Figura 6.4.1
(X [2), X [7])

368 Estructuras de datos en C Ordenamiento 369


shellsort ( x, n, incrmnts, numinc) El anlisis de la eficiencia del Shell sort est involucrado con las matemticas y
int x(), n, incrmnts(J, numinc; ms all del alcance de este libro. Los requerimientos reales de tiempo para un orde-
{ namiento especfico dependen del nmero de elementos en el arreglo incrmnts y de
sus valores reales. un requerimiento intuitivamente claro es que los elementos de
int incr, j, k, span, y;
incrmnts deberan ser primos relativos (es decir, que no tengan divisores comunes
for ( incr = O; incr < numinc; incr++) distintos de 1). Esto garantiza que iteraciones sucesivas entremezclen subarchivos de
I* span es el tamao de los incrementos *I tnanera que el archivo completo est en realidad casi ordenado cuando span es igual
span = incrmnts[incrJ;
a 1 en la ltima iteracin.
for (j = span; j < n; j++)
Se ha mostrado que el orden del Shell sort puede aproximarse por O(n (log n) 2)
I * Insertar el elemento x[j} en la posicin adecuada * !
I* en el subarchivo correspondiente * si se usa una secuencia adecuada de incrementos. Para otra serie de incrementos, se
y= x[jl; puede probar que el tiempo de ejecucin es O(nL 5). Datos empricos indican que el
for (k = j-span; k >= o && y < x[kl; k - span) tiempo de ejecucin es de la forma a * n b, donde a est entre 1.1 y l. 7 y b es ms o
x[k+spanl = x[kl; menos 1.26 o de la forma e* n * (ln(n)) 2 -d * n * ln(n), donde e est cercano a 0.3 y
x[k+spanl = y; d est entre 1.2 y l. 75. En general el Shell sort se recomienda para archivos de tama-
I* ill deor *f o moderado de algunos cientos de elementos.
!* fin defor *! Knuth recomienda la eleccin de incrementos como sigue: definir una funcin
I* fin de shellsort *! h de manera recursiva que tal que h(l) = 1 y h(i + 1) = 3 * h(i) + l. Sea x el entero
Asegurarse de entender la accin del. programa anterior sobre el archivo menor tal que h(x) ce n, y sea numinc, el nmero de incrementos, igual ax 2e
muestra de la figura 6.4.1. Obsrvese que en la ltima iteracin, donde span = 1, el incrmnts[i] igual a h(numinc - i + 1) para i desde 1 hasta numinc.
ordenamiento se reduce a una insercin simple. Se puede usar una tcnica similar al Shell sort para perfeccionar el ordena-
La idea que hay detrs de Shell sort es simple. Ya observamos que el ordena- miento de burbuja. En la prctica, una causa importante de la ineficiencia en el
miento por insercin simple es muy eficiente para un archivo que est casi ordenado. ordenamiento de burbuja no es el nmero de comparaciones sino el de intercambios. Si
Tambin es importante darse cuenta de que cuando el tamao del archivo, n, es pe- se usa una serie de incrementos para definir subarchivos a ser ordenados por burbu-
queo, un ordenamiento O(n 2) es con frecuencia ms eficiente que un ordenamiento ja de manera individual, como en el caso de Shell sort, los ordenamientos por bur-
O(n log n). La razn para esto es que los ordenamientos O(n 2) son por lo general buja iniciales se harn sobre archivos pequeos y los ltimos sobre archivos casi
muy simples de programar e implican muy pocas acciones que no sean compara- ordenados en los que son necesarios pocos intercambios. Este ordenamiento modifi-
ciones y remplazamientos en cada paso. A causa de esta pequea sobrecarga, la cado por burbuja, que requiere una sobrecarga muy pequea, funciona bien en
constante de proporcionalidad es ms bien pequea. Un ordenamiento O(n log n) es situaciones prcticas.
por lo general bastante complejo y emplea un gran nmero de operaciones suple-
mentarias en cada paso con el objetivo de reducir el trabajo en los pasos siguientes. Ordenamiento por clculo de direccin
As, su constante de proporcionalidad es mayor. Cuando n es grande, n 2 rebasa a n
* log (n), de manera que las Constantes de proporcionalidad no juegan un papel fun- Como un ejemplo final de ordenamiento por insercin, considrese la siguiente
damental en la determinacin de cul ordenamiento es ms rpido. Sin embargo, tcnica llamada ordenamiento por clculo de direccin (a veces llamada por disper-
cuando n es pequeo, n 2 no es mucho ms grande que n * log (n), de manera que sin). En este mtodo se aplica una funcin/a cada llave. El resultado de esta fun-
una diferencia grande entre d.ichas constante.s puede ocasionar que un ordenamiento cin determina en cul de varios subarchivos debe colocarse el registro. La funcin
O(n 2) sea ms rpido. debe tener la propiedad de que si x ,;; y,f(x) ,;; f(y). De una funcin tal se dice que
Como el primer incremento usado por el Shell sort es grande, los subarchivos preserva el orden. As, todos los registros en un subarchivo tendrn llaves menores o
individuales son bastante pequeos, de manera que el ordenamiento por insercin iguales que los registros en otro subarchivo. Un elemento se coloca en un subarchivo
simple aplicado a ellos es bastante rpido. Cada ordenamiento de un subarchivo trae en la secuencia correcta usando algn mtodo de ordenamiento: con frecuencia se
como consecuencia que el archivo completo est ms cerca de ser ordenado. As, usa insercin simple. Tras colocar todos los elementos del archivo original en
aunque en pasos sucesivos del Shell sort se usen incrementos ms pequeos y'en con- subarchivos, se pueden concatenar dichos subarchivos para producir el resultado
secuencia se traten subarchivos ms grandes, esos subarchivos estn casi ordenados ordenado.
debido a la accin de los pasos previos. As, los ordenamientos por insercin en esos Por ejemplo, considrese de nuevo el archivo muestra
subarchivos son tambin bastante eficientes. Es importante notar que si un archivo
est ordenado en forma parcial usando un incremento k y se ordena de manera suce- 25 57 48 37 12 92 86 33
siva usando un incremento j, el archivo permanece parcialmente ordenado en el
incremento k. Es decir, ordenamientos parciales subsecuentes no interfieren con los
anteriores.
Ordena:miento '371
370 Estructuras de d8.tos eri e
Crearemos diez subarchivos, uno para cada uno de los diez primeros dgitos p0 struct
sibles. Al inicio, cada uno de esos subarchivos est vaco. Se declara un arreglo de int info;
apuntadores/(10], donde/[i] apunta al primer elemento en el archivo cuyo primer int next;
dgito es i. Despus de examinar el primer elemento (25), ste se coloca dentro del node[NUMELTSJ;
archivo encabezado por/[2]. Cada uno de los subarchivos se guarda como una lista
I* ize available lit *I
ligada ordenada de los elementos del arreglo original. Despus de procesar cada
int avail = D;
uno de los elementos del archivo original, los subarchivos sern como los de la figu,
ra 6.4.2. for ( i = O; i < n-1; i++)
node[iJ.next = i+1;
Presentamos una rutina para implantar el ordenamiento por clculo de direc-
node[n-1].next = -1;
cin. La rutina supone un arreglo de nmeros de dos dgitos y usa el primer dgito de ! * inicializar apuntadores * /
cada nmero para asignar ese nmero a un subarchvo. for (i = O; i< 10 i++)
f(i] = -1;
#define NUMELTS fo r ( i < n ; i ++) {
O; i
/* insertar cada elemento, _en-forma sucesiva, en el *I
addr(x, n) I* stibarchivo respectivo utilizando insercin en lista . *!
int x[ J, n; y=x[il;
{ first =- y/10; !* Encontrarelprimerdgitodeun *I
int f[10), first, i, J, p, y; I * nmero de dos dgitos *I
I * Buscar en la lista ligada *I
place [&f(firstl, y);
/* place. inserta y en la posicin adecuada en la lsta *I
F(O) = nu/1 /* ligada a la que apunta f[first] */
I* fin deor *I

F(I) - - - ~ 12 null
I*
i = o;
copiar los ltimos nmeros en el arreglo x *I

for (j = O; j < 10; j++)


F(2) _ _ _.,..
25 1 nu/1 1
p=f(jl;
while (p != -1) {
x[i++l = node[p].info;

~
p = node[pl.next;
F(3) - - - _3_7_.J_n_u_ll_
Ll l.* fin de while * I
I* fin de for *I
F(4) - - -...
I * fin de. addr * /
1 48 1 nu/1 1

Los requerimientos de espacio del ordenamiento por clculo de direccin son


F<S) _ _ _.,..
1 57 1 nu/1 1 aproximadamente 2 * n (usados por el arreglo node) ms algunos nodos cabecera y
variables temporales. Obsrvese que si los datos originales se dan en la forma de una
lista ligada en lugar de un arreglo secuencial, no es necesario guardar el arreglo x y la
F(6) = nu/1
estructura ligada node a la vez.
Para evaluar los requerimientos de tiempo del ordenamiento, obsrvese lo si-
F(7) = nu/1 guiente: Si los n elementos originales estn distribuidos uniformemente de manera
aproximada sobre los m subarchivos y el valor de nlm es ms o menos!, el tiempo
de ordenamiento est prximo a O(n), dado que la funcin asigna cada elemento a
r(8) - - - 86 null su archivo correspondiente y se requiere poco trabajo extra para colocar el elemento
dentro del propio subarchivo. Por otra parte, si rtl.m es mucho mayor que 1, o si el
archivo original no est distribuido de manera uniforme sobre los m subarchivos, se
92 null Figura 6.4.2 Ordenamiento por
1-"(9) - - - - requiere un trabajo significativo para insertar un elemento en el archivo adecuado y
clculo de direccin.
el tiempo es, en consecuencia, cercano a O(n 2).

372 Estructuras de datos en e Ordenamiento 373


i. un archivo ordenado.
EJERCICIOS
ii. un archivo ordenado en orden inverso (es decir de mayor a menor)

6.4.1. El ordenamiento por insercin de dos vas es una modificacin del ordenamiento por iii. un archivo en el cual los elementos x[O], x[2J, x[4], son los menores y estn ordena-
insercin simple como sigue: Se aparta un arreglo de salida separado de tamao n. Este dos de cierta forma y los elementos x[ll, x{3J, x[S], ... son los mayores y estn en
arreglo de salida actual igual que una estructura circular como en la seccin 4.1. x[O] se orden inverso (es decir, x[O] _es el menor x[l]'es el mayor, x[2] es el siguiente ms
coloca en el elemento medio del arreglo. Una vez que un grupo contiguo de elementos pequeo, x[3] es el siguiente ms grande, y as de manera sucesiva.
est en e! arreglo, se hace espacio para un nuevo elemento recorriendo todos los ele~ iv. un archivo en el cual x[O] hasta x[ind] (donde ind = (n - 1)/2) son los menores y
mentes menores un paso a la izquierda o todos los elementos mayores un paso a la estn ordenados y en el cual x[ind + I] hasta x[n - I] son los mayores y estn en
derecha. La eleccin de la direccin en la cual se deben recorrer los elementos depende orden inverso.
de cul direccin causara la menor cantidad de corrimientos. Escribir una rutina en e v. un archivo en el cualx[O], x[2], x[4] , ... son los elementos menores en orden y x[I1,
para implantar esta tcnica. x[3], x(5] , ... son los mayores en orden.
6.4.2. El ordenamiento por insercin de intercalacin procede de la siguiente manera:
a. el ordenamiento por insercin simple
Paso I: Para todo i par entre Oy n - 2, comparar x[i] conx[i + IJ. Colocar el mayor b. el ordenamiento por insercin usando bsqueda binaria
en la posicin siguiente de un arreglo large y el ms pequeo en la siguien- c. el ordenamiento por insercin en lista
te posicin de un arreglo small. Si n es Impar, colocar x[n - 1] en la ltima d. el ordenamiento poi insercin de dos vas del ejercicio 6.4.1.
posicin del arreglo sma/1. (Large es de tamao ind, donde ind = (n - 1)/2; e. el ordenamiento por insercin de intercalacin del ejercicio 6.4.2
sma/1 eSde tamao indo ind + l, dependiendo d sin es par o impar.) f. el Shell sort usando incrementos 2 y 1
g. el SheH sort usando incrementos 3, 2 y 1
Paso 2: Ordenar el arreglo !arge usando insercin por, intercalacin de manera recur- h. el Shell sort usando incrementos 8,4, 2 y 1
siva. Siempre que 'un elemento fargeU] se mueva a /arge[k], smal(U] tambin i; el Shell sort usando incrementos 7, 5, 3 y 1
se mueve a small[k], (Al final de este paso, large[i] < = large[i + 1] para j. el' ordenamiento por clculo de direccin presentado en el teXto
todo i menor que ind y small[i] < = large[i] para todo i menor o igual que 6.4.7. En qu circunstancias se recomendara el uso de cada uno de los siguientes ordenamien-
ind. tos respecto a los otros:
a. el Shell sort.de esta seccin
Paso 3: Copiar sma/1[0] y todos los elementos de large desde x[O] a x[ind]. b, el heapsort de la seccin 6.3
c . _el quicksort. de la seccin 6.2
Paso 4: Definir el entero nm[i] como (2;+ 1 + (-l);)/3. Comenzando con i = O y
6.,4.8. De_terminar cul de_ los siguientes ordenamientos es ms eficiente:
procediendo con 1 mientras nm[i] < = (n/2) + 1, insertar los elementos de a. el ordenamiento por; insercin simple de est_a seccin
small[_nm[i + I]J hasta small[nm[i] + l] en x por turno, usando insercin
b. el orde_n.am.i_ento por seleccin directa de la seccin 6.3
binaria. (Por ejemplo sin = 20, los valores sucesivos de nm son nm[O} = c. el ordenamiento de burbuja de 1~ seccin 6.2.
1, ntm[l] = 1 nm[2] = 3, nm[3] = 5 y nm[4] = 1!, que es igual a (n/2)
+ 1. As, los elementos de small se insertan en el siguiente orden: smal/[2],
sma/1(1]; despus sma/1[4], sma/1[3]; despus sma/1[9], sma/1[8], sma/1(7],
smal/[61, sma/1[5]. En este ejemplo no hay sma/1(10].) ORDENAMIENTOS POR INTERCALACION Y DE BASE

Escribir una r,utina en. C para implantar esta tcnica.


Ordenamientos por intercalacin
6.4.3. Modifique el quicksort de la seccin 6:2 de manera que use un ordenamiento por inser-
cin simple cuando un subarchivo tenga un tamao por debajo de s. Determine. de
manera experimental qu valor des debera usarse para obtener una eficiencia mxima. Intercalacin es el proceso de combinar dos o ms archivos ordenados en un
tercer archivo ordenado. Un ejemplo de una rutina que acepta dos arreglos .ordena-
6.4.4. Pruebe que si un archivo se ordena en forma parcial, usando un inc;rementoj en el Shell
sort, se mantiene parcialmente ordenado en dicho incremento aun despus de ser orde- dos a y b den 1 y n2 elementos respectivamente, intercalndolos dentro de un tercer
nado para otro incremento k. arreglo e que contiene n3 elementos, es el siguiente:
6.4.5. Explique por qu es. deseable escoger todos los incrementos. del Shell sort de manera
que sean primos relativos. mergearr(a, b, e, n1, n2, n3)
6.4.6. Cul es el nmero de comparaciones e intercambios (en trminos del tamao del int a[J, b[), c(J, n1, n2, n3;
archivo n) ejecutados con cada uno de los mtodos de ordenamiento (del a alj) para loS {
siguientes archivos: int apoint, bpoint, cpoint;
int alimit, blirnit, climit;

Ordenamiento 375
374 Estructuras de datos en G
alimit n1-1; Archivo 1251 1571 1481 1371 112] 1921 1861 1331

y y y y
blimit n2-1; original
climit n3-1;
if (n1+n2 ! n3) {
printf("incompatibilidad en los lmites del arreglo/n");
Paso

yy
exit(l); 125 571 137 48] 112 92] 133 861
1
I* fin deif *f
!* apoint y bpoint son indicadores de que tanto se ha *I
!* avanzado en los arreglos a y b *!
apoint = O;
Paso
bpoint = o; 2
125 37 48 57] {12 33 86 92]
for (cpoint = O; apoint <= alimit && bpoint <= blimit;

if (a(apointl< b(bpointl)
c[cpointJ a[apoint++J;
Paso 92]
else 1i,2 25. 33 37 48 57 86
3
c(cpointl b(bpoint++J;
FiQ~ra.6.5.1 .:Pasos sucesivos del ordenamiento por intercalacin.
while (apoint < alimit)
c[cpoint++J = a[apoint++J;
while (bpoint< blimit)
c(cpoint~+l b(bpoint++l; k = O; /* k es el indice del arreglo auxiliar *I
I * fin de mergearr * I while (11+size < n) { /* veriiquesiexistendos *I
/* archivos para mezclar *I
Podemos usar esta tcnica para ordenar un archivo de la siguiente manera. Di-
I* cplcule los ndices r?sultantes */
12 11+size;
vidir el archivo en n subarchivos de tamao 1 e intercalar pares adyacentes (incone- u1 12-1; 1

1
xos) de archivos. Entonces tenemos ms o menos n/2 archivos de tamao 2. Repetir u2 (12+size-1 <~) ? 12~sie-1 : n-1;
el proceso hasta que slo reste un archivo de tamao n. La figura 6.5.l ilustra cmo I*
opera este proceso en un archivo muestra. Cada archivo individual est entre corchetes.
Proceder a trav~s de los dos subarchivos
for (i 11, j 12; i < u1 && j < u2; k++)
*I
fl
Presentamos una rutina para implantar la descripcin anterior de un ordena- I* colocar el- menor en el arreglo aux */
miento por intercalacin directa. Se requiere de un arreglo auxiliar aux de tamao n if (X(i] <"" x[j])
para guardar los resultados de intercalar dos subarreglos de x. La variable size con- aux[kl x(i++l;
tiene el tamao de los subarreglos que se estn intercalando. Como en todo momen- else
to, los dos archivos que estn fusionndose son subarreglos de x, se requieren lmites aux(kl x(j++l;
I* En este punto uno de los subarchivos *I
superiores e inferiores para indicar los subarchivos de x que se estn intercalando. 11
I* se ha termini.do. Insertar la *I
y u! representan los lmites inferior y superior del primer archivo y /2, u2 los del I* parte restante del Otro archivo *I
segundo. i y j se usan para hacer referencia a elementos de los archivos fuente que se for {; i <= u1; k++)
estn intercalando y k indexa el archivo destino aux. La rutina se presenta a conti- aux(kl x(i++l;
nuacin: far (; j <= u2; k++)
aux(kl x(j++J;
#define NUMELTS /* avance 11 al inicio-del siguiente *I
I* par de archivos *I
mergesort ( x, n) 11 u2+1;
int x[ J, n; I * fin de while * I
{ /* copiar cualquier porcin restante de un archivo individual */
int aux(numeltsJ, i, j, k, 11, 12, size, u1, u2; far (i = ..11; k < n; i++)
aux(k++J x(il;
si z e = 1 ; / * interqalar archivos de tamao 1 * I
I* copiar aux en x y ajustar size *I
while (size < n) 1
11 = O ; / * inicializar el lmite inferior del primer archivo

376 Estructuras de datos en C Ordenamiento 377


for ( i = o; i < n; i++) El ordenamiento por intercalacin tambin puede ser presentado de manera
x(i) = aux(i]; bastante natural como un proceso recursivo en el cual las dos mitades del arreglo se
size *= 2; ordenan primero en forma recursiva usando ordenamiento por intercalacin y una
! * fin de while *! vez ordenadas se unen por intercalacin. Para los detalles, ver el ejercicio 6.5. l y el
/* fin de mergesort */ 6.5.2. Ambos, el ordenamiento por intercalacin y el quicksort, son mtodos que
implican la divisin del archivo en dos partes, ordenando ambas por separado y jun-
Hay una deficiencia en el procedimiento anterior que es fcil, de remediar: si el tndolas luego. En el ordenamiento por intercalacin la divisin es trivial (se toman
programa ha de ser prctico para arreglos grandes. En lugar de intercalar cada con- ;implemente las dos mitades) y la unin es difcil (intercalar los dos archivos orde-
junto de archivos en el arreglo auxiliar aux y luego volver a copiar aux dentro de x, nados). En el quicksort, la divisin es dificil (particionar) y la unin es trivial (las dos
se pueden ejecutar intercalaciones alternas de x a aux y de aux ax. Dejamos esta mitades y el pivot forman de manera automtica un arreglo ordenado).
El ordenamiento por insercin puede considerarse como un caso especial del
modificacin como ejercicio al lector.
ordenamiento por intercalacin en el cual las dos mitades constan de un solo elemen,
Es obvio que no hay ms de log 2 n pasos en el ordenamiento por intercalacin
to y el resto del .arreglo. El ordenamiento por seleccin puede ser considerado un
y cada uno implica n o menos comparaciones. As, el ordenamiento por intercala-
cin requiere de no ms den * log 2 n comparaciones. En realidad puede mostrarse caso especial del quicksort en el cual el archivo se particiona en una mitad que consta
que el ordenamiento por intercalacin requiere menos comparaciones que n * log2 slo del mayor elemento y .otra mitad que contiene el resto del arreglo.
n - n + 1, en promedio, comparado con 1.386 * n * log 2 n comparaciones en pro-
medio para el quicksort. Adems, el quicksort puede requerir (n 2) comparaciones El algoritmo de Cook-Kim
en el peor caso, mientras que el ordenamiento por intercalacin nunca requiere ms
den* log 2 n. Sin embargo, el ordenamiento por intercalacin requiere aproximada- Con frecuencia se sabe que un archivo est casi ordenado salvo por unos pocos
mente dos veces ms asignaciones que el quicksort en promedio, aun cuando se elmentos. O se puede saber que un archivo de entrada es probable que est ordena-
alternen intercalaciones de x a aux y de aux ax. do. Para archivos pequeos que estn casi ordenados o para archivos ordenados, la
El ordenamiento por intercalacin requiere tambin espacio adicional O(n) insercin simple es el ordenamiento ms rpido considerando comparaciones y asig-
para el arreglo auxiliar, mientras que el quicksrt requiere slo O(log n) espacios naciones) que se ha encontrado. Para archivos grandes o archivos que estn un poco
adicionales para la pila. Se ha desarrollado un algoritmo para una intercalacin en el desordenados el ordenamiento ms rpido es el quicksort usando el elemento medio
lugar de dos subarreglos ordenados en tiempo O(n). Este algoritmo podra permitir como pivot. (Si se consideran slo las comparaciones, el ms rpido es el ordena-
que el ordenamiento por intercalacin se volviese un ordenamiento O(n lag n) en el miento por intercalacin.) Sin embargo, existe otro algoritmo hbrido descubierto
lugar. Sin embargo; esta tcnica requiere de un gran nmero de asignaciones y no por Cook y Kim que es ms rpido que el ordenamiento por insercin y el qulcksort
seria, por lo tanto, tan prctica como encontrar el espacio O(n) suplementario. usando el elemento medio para entradas casi ordenadas.
Hay dos modificaciones de procedimiento anterior que pueden resultar en un El algoritmo de Cook-Kim opera de la siguiente manera: Se examina la entra-
ordenamiento ms eficiente. La primera de ellas es intercalacin natural. En la inter- da para pares desordenados de elementos (por ejemplo, x[k] > x[k + !]). Los dos
calacin directa, los archivos son todos del mismo .tamao (excepto quizs el elementos en un par no ordenado se eliminan y se agregan al final de un nuevo
ltimo). Podemos sin embargo, explotar algn orden existente entre los elementos y arreglo. Se examina el siguiente par que consta en el predecesor y sucesor del par eli-
definir los subarchivos como los subarreglos rns largos de elementos crecientes. Se minado, tras la eliminacin de un par desordenado. El arreglo original, con los pares
deja como ejercicio la codificacin de tal rutina .. desordenados eliminados, est ahora de cierta forma ordenado. Despus se ordena
La segunda modificacin usa asignacin ligada en lugar de secuencial. Agre- el arreglo de pares desordenados usando el quicksort por elemento medio si contiene
gando un campo apuntador a cada registro, se puede eliminar la necesidad del ms de 30 elementos y por insercin simple en caso contrario. Entonces, los dos
arreglo auxiliar aux. Esto se puede hacer ligando de manera explcita cada subarchi- arrglos se intercalan.
vo de entrada y salida. La modificacin se puede aplicar tanto a la intercalacin El algoritmo de Cook-Kim explota ms lo ordenado de la entrada que cual-
directa como a la natural. Lo dejamos como ejercicio al lector. quier otro ordenamiento y es mucho mejor que el quicksort por elemento medio, el
Notar que usando ordenamiento por intercalacin sobre una lista ligada se eli- ordenamiento por insercin, el ordenamiento por intercalacin o el Bsort para
minan sus desventajas relativas al quicksort: no se requiere de espacio adicional sig- entradas casi ordenadas. Sin embargo, para entradas gener~das de manera aleatoria
nificativo ni de movimientos de datos considerables. Por Jo general, los datos el algoritmo de Cook-Kim es menos eficiente que el Bsort (y desde Juego que el
pueden ser complejos y grandes, de manera que la asignacin de los mismos requiere quicksort o el ordenamiento por intercalacin). En consecuencia, el quicksort por
ms trabajo que la reasignacin de apuntadores que es an necesaria en un ordena- elemento medio, el ordenamiento por intercalacin o el Bsort, son preferibles cuan-
miento por intercalacin basado en una lista. do son probables archivos grandes de e11trada ordenados aunque tambin se requiere
un comportamiento aleatorio de la entrada.

378 Estructuras de datos en C Ordenamiento 379


Ordenamiento por base ificativa. Esto permite procesar el archivo completo sin subdividir los archivos Y
Sign
sin perder de vista dnde comienza y termma cada subarc h'1vo. La r1gura 6..52
El siguiente mtodo de ordenamiento que consideramos se llama ordenamien- ilustra este ordenamiento aplicando al archivo muestra
to por base. Este ordenamiento se basa en los valores de los dgitos presentes en las
representaciones posicionales de los nmeros que se estn ordenando. Por ejemplo, 25 57 48 37 12 92 86 33
el nmero 235 en notacin decimal se escribe como un 2 en las centenas, un 3 en las
decenas y un 5 en las unidades. El mayor de dos enteros de igual longitud en esa . Asegrese de que se pueden seguir las acciones descritas en los dos pasos de la figura
notacin se puede determinar de la siguiente manera: Comenzar en el dgito ms sig- 6.5.2.
nificativo y avanzar hacia los menos significativos siempre y cuando los dgitos En consecuencia podemos esbozar un algoritmo para ordenar del modo antes
correspondientes de ambos nmeros coincidan. El nmero con el dgito mayor en la descrito como sigue:
primera posicin en que ambos nmeros no coinciden ser entonces el mayor. Por
supuesto, si todos los dgitos de ambos nmeros coinciden, los nmeros .son iguales: for (k = dgito menos significativo
Podemos escribir una rutina de ordenamiento basada en el mtodo anterior, k <= . dgito ms significa_tivo k++) {

Usando la base decimal, por ejemplo, los nmeros se pueden repartir en diez grupos for ( i o; i = < n; i++) {
basados en sus dgitos ms significativos. (Por sencillez, suponemos que todos los y=x[il;
j = k~sim dgito de y;
nmeros tienen la misma cantidad de dgitos, agregando ceros no significativos si es colocar y al final de queue{j/;1
necesario). As, cualquier elemento en el grupo "O" es menor que todo elemento en
el grupo" 1", cuyos elementos son, todos menores que cualquier elemento en el gru-
po "2" y as sucesivamente. Entonces podemos ordenar dentro de los grupos indivi- Archivo original
duales basados en el siguiente dgito ms significativo. Repetimos este proceso hasta 25 57 48 37 1~ ()'1 86 33
que cada subgrupo haya sido subdividido de manera que los dgitos menos significa- Colas basadas en dgitos menos significativos.
tivos estn ordenados. En este momento, el archivo original ha sido ordenado. (Ob-
srvese que la divisin de un subarchivo en grupos con el mismo dgito en una posicin Frente Final
dada es similar a la operacin partition en el quicksort, en la cual. un subarchivo se queue [OJ
divide en dos grupos basado en la comparacin con un elemento particular.) Este queue [ 1J
queue[2] 12
mtodo se llama a veces el ordenamiento por intercambio de base; su programacin queue [31 33
se deja al lector como ejercicio. queue [4]
Consideremos una alternativa al mtodo anterior. De la anterior discusin queue[5] 25
queue [61 86
parece ser que hay una gran cantidad de contabilidad involucrada en la constante queue[?] 57 37
subdivisin de los archivos y la distribucin de sus contenidos en los subarchivos queue [8] 48
basada en dgi_tos particulares. Con seguridad, sera ms fcil que pudiramos proce- queue [91

sar el archivo completo como un .todo en lugar de tratar con muchos archivos indivi-
Despus del primer paso:
duales. 92 33 25 86 57 37 48
t2
Suponer que llevamos a cabo las siguientes acciones en el archivo para cada
dgito comenzando con el menos significativo y terminando con el ms significativo. Colas basadas en el dgito ms significativo.
Tmese cada nmero en el orden en que aparece en el archivo, y colquese dentro de
Frente Final
una de diez colas, dependiendo del valor del dgito que est siendo procesado ..Des-
queue [O]
pus pngase cada cola en el archivo original comenzando con la de los nmeros de
queue [ t J 12
dgito O y terminando con la de nmeros de dgito 9. Cuando se haya ejecutado lo queue [21 25
anterior para cada dgito, comenzando con el menos significativo y terminando con queue 13 J 33 37
el ms significativo, el archivo est ordenado. Este mtodo de ordenamiento se lla- queue [4] 48
queue[SI 57
ma ordenamiento por base. queue[6]
Obsrvese que este esquema de ordenamiento ordena primero los dgitos me- queue [7 J
nos significativos. As, cuando todos los nmeros estn ordenados en un dgito ms queue [8! 86
queue {91 92
significativo, aquellos que tienen el mismo dgito en esa posicin pero dgitos dife- Figura 6.5.2 llustracn del
An;hivo
rentes en una posicin menos significativa ya estn ordenados en la posicin menos ordenado: 12 25 33 37 48 57 86 92 ordenamiento por base.

380 Estructuras de datos en C Ordenamiento 381


/* fin de for */ !* extraer el k~simo dgito *I
for (qu = o; qu < 10; qu++) exp = power(LD,k-1); I* elevar 10 a la *!
colocar los elementos de queue[qu} en la siguiente posicin
I * (k~l)~sima potencia *!
j = (y/exp)%10;
! * fin de for *I I * insertar y en queue[il */
q = rear[jl;
Presentamos ahora un programa para implantar el ordenamiento anterior if (q == -1)
sobre nmeros de 111-dgitos. Con el objetivo de ahorrar una gran cantidad de traba- front[jl = p;
jo en el procesamiento de las colas (en especial en el paso donde regresamos los ele- else
mentos de la cola al archivo original) escribimos el programa usando asignacin node[gJ.next p;
ligada. Si la entrada inicial a la rutina es un arreglo, se convierte primero dicha rear[jl = p;
I* fin de while *I
entrada en una lista lineal ligada; si la entrada original ya est en formato ligado, es-
!* En este punto cada registro est en la cola adecuada *I
te paso no es necesario y de hecho, se ha ahorrado espacio. Esta es la misma !* de acuerdo con el dgito k. Formar ahora una lista simple a partir *!
situacin que en la rutina addr (ordenamiento por clculo de direccin) de la seccin I* de todos los elementos de la cola. Encontrar el, primero *I
6.4. Como en programas previos, no hacemos llamadas internas a rutinas sino que for (j = D; j < 10 && front[jl == -1; j++)
realizamos su accin en el lugar.
first = front[jl;
#define NUMELTS I* enCadgmar las colas restantes */
whife (j <= 9) { !* ve.icarsisetermin *!
radixsort(x, n) I * enciontrar el siguiente 'elemento * I
int x[J, n; for (i = j+1; i < 10 && front[il == -1; i++)
{
int front(l,DJ, rear[l.DJ; if(i<=9){
struct { p = i;
int info; node[rear[jll.next front[il;
int next; !* fin deif o+s/ i
node[NUMELTS]; j = i i 1
int exp, first, i, j, k, p, q, y; I* fin de while *l
!* inicializar la lista ligada *I node[rear[pJJ.n'ext = -1;' !
far (i = O;. i< n-1; i++) I* fin deor *I
node(iJ.info = x[iJ; I* reescribir el arreglo original */
node(i).next = 1+1; for (i = D; i< n; i++) {
!* fin defor *I. x[i] = node[firstJ.info;
node[n-1].info = x[n-1]; first = node[first].next;
node[n-1].next = -1; I * fin de for * I
first = O; /* irst es la cabeza de la lista ligada *I / * fin de radixsort * I
for (k = 1; k< 5; k++) {
I * suponer que se tienen nmeros de cuatro dgitos * I Los requerimientos de tiempo del ordenamiento por base dependen del nme-
for (i = O; i< 10; i++) {
ro de dgitos (111) y d.el nmero de elementos eh el archivo (n). Como el ciclo externo
!* inicializar las colas *I
rear[iJ = -1;
for (k 1; k = m; k + +) se recorre m veces (una para cada dgito) y ciclo interno n
front[il = -1; veces (una para cada ordenamiento del archivo), el ordenamiento es. ms o menos
!* fin deor *I 0(111 * n). As, el ordenamiento es razonablemente eficiente si el nmero de dgitos
I* plocesar cada elemento de la lista *! en las llaves no es demasiado grande. Deber observarse, sin embargo, que muchas
while ( first ! = -1) 1 mquinas tienen facilidades en el hardware parn ordenar dgitos de un nmero (en
p = first; esp~cial si estn en binario) mucho ms rpido de lo que pueden ejecutar una com-
first = node[firstJ.next; paracin de dos llaves completas. Por lo tanto no es. razonable comparar la estima-
y= node[pl.info; cin 0(111 * n) con algunos de los otros resultados a los que llegamos en este
captulo. Ntese tambin que si las llaves son densas (es decir, si casi todo nmero

382 Estructuras de datos en C Ordenamiento 383


que sea una posible llave lo es en realidad), m se aproxima a log n de manera que a. Escribir una rutina en C que implante este mtodo.
O(m * n) se aproxima a O(n log n). El ordenamiento requiere de espacio para alma- b. En qu casos este mtodo es ms eficiente que el mtodo del texto? En cules es
cenar apuntadores al frente y al final de las colas, adems de un campo extra en cada menos eficiente?
registro que se usa como apuntador en las listas ligadas. Si el nmero de dgitos es 6.5.5, Considere el siguiente mtodo (!!amado intercalacin binaria) de intercalar dos arreglos
grande, es a veces ms eficiente ordenar el archivo aplicando primero el ordenamien- ordenados a y ben un arreglo e: Sean la y lb el nmero de elementos en a y b, respecti-
to por base a los dgitos ms significativos y usando luego insercin directa en el vamente Y suponer que la > = lb. Dividir a en, ms o menos, lb + l subarreglos
iguales. Comparar b[O] con el elemento menor del segundo subarreglo de a. Si b[O] es
archivo redispuesto. En casos en que la mayora de los registros en el archivo tengan
menor encontrar a[iJ tal que a[i] < = b{OJ < = a[i + 1] mediante bsqueda binaria en
dgitos ms significativos diferentes, este proceso elimina pasos innecesarios para
el primer subarreglo. Ponga todos los elementos del primer subarreglo a partir de a
los dgitos menos significativos. inclusive a[i] en e y luego ponga b[OJ en c. Repetir este proceso eon b[I], b[2], ... , bUJ,
donde bU] es mayor que el menor elemento del segundo subarreglo. Ponga los elemen-
tos restantes del primer subarreglo y e! primer elemento del segundo en c. Despus
comparar bUI con el elemento menor del tercer subarreglo de a y as de manera sucesiva.
EJERCICIOS
a. Escriba un programa para implantar la intercal.cin binaria.
b. Muestre que si la = lb, la intercalacin binaria acta como la intercalacin descrita
6.5. J. Escriba-un algoritmo para una rutina mrge(x~ lb 1~ ub2) que suponga que x[lb 1] hasta en el texto.
x[ub 1J y x[ub 1 + 1J hasta x[ub2] estn ordenados e intercalar a ambos en x[lb 1J hasta
c. Muestre que si lb:= 1, la intercalacin binaria acta como la intercalacin del ejer-
x[ub2]. cicio previo.
6.5.2. Considere la siguiente versin recursiva del ordenamiento por intercalacin que usa la 6.5.6. Determine el nmero de comparaciones (como funcin de m y n) que se ejecutan al in-
rutina merge del ejercicio previo. La rutina se llama al inicio mediante msort2(x, O,
tercalar dos archivos ordenados a y b de tamao n y m respectivamente, para cada uno
n - 1). Reescriba la rutina eliminando la recursin y simplificando. Cmo difiere la de !os siguientes mtodos de intercalacin, con cada uno de los siguientes conjuntos de'
rutina resultante de la del text? archivos ordenados.
Mtodos de intercalacin:
msort2(x, lb, ub) a. el mtodo de intercalacin presentado en el texto
{ b. !a intercalacin del ejercicio 6.5.4
if (lb ! ub) { c. la intercalacin binaria del ejercicio 6.5.5.
rnid (ub+lb)/2;
msort2(x, lb, mid); Conjuntos de archivos:
msort2(x, mid+1, ub);
merge(x, lb, mid, ub); a. 111 n y u[i] < b[i] < a[i + I] para toda i
!* fin de il *I b. 111 = n y a [11] < b[l]
I* fin de msort2 */ c. 111 n y a[n/2] < b[I] < b[m] < a[(n/2) + I]
d. n 2 ' 111 y a[i] < b[i] < a[i + !] para toda i entre O y m - 1
e. n = 2 * m y a[m + i) < b[il < a[m + i + l] para toda i entre O y m -
6.5.3. Sea a(/1, /2) el nmero promedio de comparaciones necesarias para intercalar dos
f. n = 2 * 111 y a[2 * i] < b[i] < a[2 * i + !] para toda i entre O y m -
arreglos ordenados de longitud ff y /2 respectivamente, donde los elementos de los
g. 111 1 y b[OJ a[n/2]
arreglos estn elegidos al azar entre /1 + 12 elementos.
h. 111 = 1 y b[O] < a[O]
a. Cules son los valores de a(ll, 0) y a(O, /2)? i. 111 1 y a[m < b[OJ
b. Muestre que para /1 >O y /2 >O, a(/1, /2) es igual a (11/(/1 + 12))' (l + a(/1 - !,
/2)) + (/2/(/1 + 12))' (1 + a(/1, 12 - !)). (Sugerencia: exprese el nmero prome-
6.5.7. Genere dos archivos aleatorios de tamao 100 e interclelos usando cada uno de los
dio de comparaciones en trminos del nmero promedio de comparaciones tras la mtodos del ejercicio previo, sin perder de vista el nmero de comparaciones realiza-
das. Haga lo mismo para dos archivos de tamao 10 y dos de tamao 1000. Repita el
primera comparacin).
experimento 10 vces. Qu indican los resultados acerca de la eficiencia promedio de
c. Muestre que a(/1, /2) es igual a (/1 * /2 * (/1 + /2 + 2))/((/1 + !) * (/2 + !)).
d. Verifique la frmula del inciso c para dos arreilos, uno de tmao 2 y 'otro de ta- los mtodos de intercalacin?
mao I. 6.5.8. Escriba una rutina que ordene un archivo aplicando primero e! ordenamiento por base
a los r dgitos ms significativos (donde res una constante dada) y luego una insercin
6.5.4. Considre el siguiente mtodo para intercalar dos arreglos a y ben un arreglo e: Reali-
directa para ordenar el archivo completo. Esto elimina pasos excesivos en dgitos de
zar una bsqueda binaria para b[OJ en el arreglo a. Si b[OJ est entre a[i] y a[i + J] po-
ner a[l] hasta a[i] en el arreglo e, luego poner b{OJ en c. Despus ejecutar una bsqueda menor orden qt!e. pueden no ser necesarios.
binaria para b[I] en el subarreglo a[i + 1] a a[/a] (donde la es el nmero de elementos 6.5.9, Escriba un programa que imprima todos los conjuntos de seis enteros positivos al, a2,
del arreglo a) y repetir el proceso. Repetir este procedimiento para todo elemento del a3, a4, a5 y a6, de manera que
arreglo b.

384 Estructuras de datos en C Ordenamiento 385


al < = a2 < = a3 < = 20
al < a4 < = a5 < = a6 < = 20

y la suma de los cuadrados de a 1, a2 y a3 sea igual a la suma de los cuadrados de a4, a5


y a6. (Sugerencia: genere todas las posibles sumas de tres cuadrados Y usar un procedi~
miento de ordenamiento para encontrar duplicados.)
Bsqueda

En este captulo consideramos mtodos de bsqueda en grandes cantidades de datos


para encontrar un fragmento de informacin en particular. Como veremos, ciertos
mtodos de organizacin de datos hacen ms eficiente el proceso de bsqueda: Ya
que la bsqueda es una tarea tan comn en prgramacin, el conocimiento de dichos \

mtodos es un gnfo avnce en el camino a recorrer para llegar a ser un buen progra-
mador.

TECNICAS BASICAS DE BUSQUEDA

Definamos algunos trminos antes de considerar tcnicas especficas de bsqued.


Una tabla o un archivo es un grupo de elementos; cada uno de los cuales se llama
registro. Hay na llave asociada a cada registro, que se usa para diferenciar unos de
ot'ros. La asociacin entre un registro y su llave puede ser simple o compleja. En la
forma ms simple, fa llave est contenida dentro del registro en un tramo a una dis-
tancia especfica de.! principio del mismo. Una llave de ese tipo se llama llave interna
o llave incluida. En otros casos hay una tabla de llaves diferente que incluye apunta-
dores a los registros. Tales llaves se llaman externas.
Para todo archivo existe por lo menos un conjunto de llaves (tal vez ms) que
es nico (es decir, no existen dos registros que tengan el mismo valor de llave). Dicha
llve se llama llave primaria. Por ejemplo, si el archivo. est almacenado como un
arreglo, el ndice dentro del arreglo de un elemento es uha llave externa nica para
ese elemento. Sin embargo, como cualquier campo de un registro puede servir como
la llave en una aplicacin particular, las llaves no siempre necesitan ser nicas. Por

387
Estructuras de datos en C
386
ejemplo, en un archivo de nombres y direcciones, si se usa el estado (provincia) co- KEYTYPE .. . un tipo de llave
typedel !* *!
mo llave para una bsqueda particular, es probable que no sea nico, dado que typedel, RECTYPE .. . I* un tipo de registro *I
puede haber dos registros con el mismo estado dentro del archivo. Una llave de ese RECTYPE nullrec = .. . !* un registro "null'.' *I
tipo se llama llave secundaria. Algunos de los algoritmos que presentamos suponen
llaves nicas; otros permiten llaves duplicadas. Cuando se adopte un algoritmo para KEYTYPE keyfunct( r)
una aplicacin particular, el programador debe saber si las llaves son nicas y asegu- RECTYPE r;
rarse de que el algoritmo seleccionado es el adecuado. { ...
Un algoritmo de bsqueda es un algoritmo que acepta un argumento a y trata };
de encontrar un registro cuya llave sea a. El algoritmo puede dr como resultado el
registro entero o, lo que es ms comn, un apuntador a dicho registro. Es posible
Podemos entonces representar el tipo de datos abstracto table como un simple
que la bsqueda de un argumento particular en una tabla no sea exitosa, es decir,
conjunto de registros. Este es nuestro primer ejemplo de un ADT definido como un
que no exista registro en la tabla que tenga como llave ese argumento. En tal caso el
conjunto y no como una secuencia. Usamos la notacin [eltype] para denotar
algoritmo puede dar como resultado un "registro nulo" especial o un apuntador
n conjunto de objetos de tipo eltype. La funcin inset(s, elt) da como resultado
nulo. Si la bsqueda es infructuosa, con mucha frecuencia, es deseable agregar un
verdadero si el/est en el conjuntos y.falso en caso contrario. La operacin de con-
nuevo registro con dicho argumento como llave. Un algoritmo que haga esto se lla-
juntos x y denota el conjunto x eliminando de l .todos los elementos de y.
ma algoritmo de bsqueda e insercin. A una bsqueda exitosa se le llama con fre-
cuencia una recuperacin.
abstract typedef [rectype] TABLE (RECTYPE);
A una tabla de registros en la cual se usa una llave para recuperacin se le lla-
ma con frecuencia tabla de bsqueda o diccionario. abstract member(tbl,k)
En algunos casos es deseable insertar un registro con una llave primaria key eri TABLE(RECTYPE) tbl;
un archivo sin buscar primero otro registro con la misma llave. Tal situacin podra .KEYTYPE k;
surgir si ya se determin que no existe un registro tal en el archivo. En discusiones postcondition if(existe un r en tbl tal que keyiunct(r) = = k)
posteriores investigamos y comentamos acerca de la relativa eficiencia de varios, al-
goritmos. En tales casos el lector debe observar si los comentarios se refieren a una then member TRUE
bsqueda, a un.a inserc_in o a una bsqueda e insercin. , else member FALSE
. Obsrvese que no hemos dicho ~ada acerca de la forma.en la cual est organi-
abstract RECTYPE search(tbl,k)
zada la tabla o el archivo. Puede ser un arreglo de registros, una lista ligada, un TABLE(rectype) tbl;
rbol o incluso un diagrama. Ddo que distintas tcnicas de bsqueda pueden,,ser keytype k;
adecuadas para organizaciones de tablas diferentes, con frecuencia se disea una postcondition (not member(tbl, k)) && (se~rch == nullrec)
tabla teniendo en mente una tcnica de bsqueda especfica. La tabla puede estar 1 t (m~mber(fbl,k) && keyfunct(s~arch) == k);
contenida en su totalidad en la memoria, en la memoria auxiliar o .estar .dividida en
ambas. Es claro que son necesarias diferentes tcnicas de bsqueda bajo esas distin- abstract inst(t'bl,~)
tas suposiciones. La bsqueda.en la cual toda la tabla est de manera frecuente en la TABLE(RECTYPE) tbl;
memoria principal se llama bsqueda interna, mientras que la bsqueda en la que la RECTYPE r;
precondition member(tbl,keyfunct(r)), FALSE
mayor parte de la tabla .est en la niemoria auxiliar se .llama bsqueda externa.
postcondition inset(tbl, r);
Como en el ordenamiento, nos concentramos de manera primordial en la bsqueda
(tbl - [ r l ) - - tbl';
interna; sin embargo mencionamos algunas tcnicas de bsqueda externa cuando
es.tn muy relacionadas con los mtodos que estudiamos. abstract delete(tbl, k)
TABLE(RECTYPE) tbl;
KEYTYPE k;
El diccionario como un tipo de datos abstracto postcondition tbl = (tbl 1 ~ [search(tbl,k)]);

Una tabla de bsqueda o diccionario puede representarse como un ADT (tipo Como no se presume que exista relacin entre los registros o sus llaves
de datos abstracto). Primero suponemos dos tipos de declaraciones de los tipos de asociadas, la tabla qu,e especificamos se llama tabla desordenada. Aunque una tabla
llave y registros y una funcin que extrae la llave de un registro del mismo. Tambin como esa permite que los elementos sean recuperados con base en los valores de sus
definimos un registro nulo para representar una bsqueda infructuosa. llaves, los elementos no pueden recuperarse en un orden especifico. Hay oeasio-

388 Estructuras de datos en C


Bsqueda 389
nes en las que, adems de las facilidades que proporciona una tabla desordenada
registros de r(O) a r(n - 1) de tal manera que k(i) es la llave de r(i). (Obsrvese que
tambin es necesario recuperar elementos basndose en algn ordenamiento de su;
estamos usando la notacin algortmica, k(i) y r(i) como se describi de manera pre-
llaves. Una vez establecido un ordenamiento entre las llaves, se hace posible la refe.
via.) Supongamos tambin que key es un argumento de bsqueda. Queremos. obtener
rencia al primer elemento de una tabla, al ltimo y al sucesor de un elemento dado.
eLentero i ms pequeo tal que k(i) sea igual a key si existe tal i y -1 en caso contra-
Una tabla que cuente con estas facilidades adicionales se llama una tabla ordenada. El
ro. El algoritmo para hacerlo es el siguiente:
ADT para una tabla ordenada debe especificarse como una secuencia para indicar el
ordenamiento de los registros y no como un conjunto. Dejamos la especificacin
tor (i = o; i < n; i++)
de ADT como ejercicio al lector. i f (key k(i))
return(i);
Notacin algortmica return (-1);

La mayora de las tcnicas presentadas en este captulo se presentan como al-


El algoritmo examina cada llave en turno; al encontrar una que coincida con el argu-
goritmos en lugar de programas en C. La razn para ello es.que una tabla puede
mento de la bsqueda, da como resultado su ndice (que acta como apuntador a su
representarse en una amplia variedad de formas. Por ejemplo, una tabla (llaves ms
registro). Si ninguna coincide el resultado es -1.
registros) organizada como un arreglo podra declararse _mediante:
Este algoritmo puede modificarse con facilidad para agregar un registro rec
con llave key a la tabla si key an no est en la mi3ma. La ltima instruccin se
#define TABLESIZE 1000 modifica como sigue:
typedef KEYTYPE
typedef RECTYPE
k(n) = key; I* insertar la nueva llave */
struct {
KEYTYPE k;
r(n) = rec; I* y el registro */
RECTYPE r; n+ +; *
I incrementar el tamao de la tabla */
table[TABLESIZE); return(n - 1);

o como dos arreglos separados: Obsrvese que si se hacen inserciones usando. slo el algoritmo modificado an-
terior, dos registros no pueden tener la misma llave. Cuando este algoritmo se
KEYTYPE k[TABLESIZEJ; implanta en C, debemos asegurarnos que el incremen.to de n no haga que su valo.r
RECTYPE r[TABLESIZEJ; exceda el lmite superior del arreglo. Para usar la bsqueda secuencial con insercln
en un. arreglo, debe haberse asignado cqn ameroridad memoria suficiente para el
En el primer caso la i-sima llave sera referida como table[i].k; en el segundo como .mismo. ,
k[i]. .
Un. mt_odo de bsqueda an.ms eficiente in.volucra la insercin de la llave.del
De manera similar, para una tabla organizada como una lista, podra usarse la
argumento al final del arreglo antes de comenzar la bsqueda, garantizando as que
representacin dinmica de una lista o la representacin con arreglo de una lista. En
la llave ser encontrada.
el primer caso la llave del registro apuntado por un apuntador p sera referida como
node[p].k; en el ltimo, como p - k.
k(n) key;
Sin embargo, las tcnicas para buscar ~n esas tablas son muy similares. As, for (i D; key != k(i); i++)
con el objeto de liberarnos de la necesidad de elegir una representacin especfica,
adoptamos la convencin algortmica de hacer referencia a la llave i-sima como k(i) if(i<n)
y a la llave del registro apuntado por p como k(p). De igual forma, hacemos referen- return(i);
cia al registro correspondiente como r(i) o r(p). De esta manera podemos concentrar lse
nuestra atencin en los detalles de la tcnica en lugar de los de la implantacin. return (-1);

Bsqueda secuencial Para una bsqueda e insercin, la instruccin if completa se remplaza por

La forma ms simple de bsqueda es la bsqueda secuencial. Esta bsqueda es i f ( i = n) .


r ( n++), = rec;
aplicable a una tabla organizada, ya sea como un arreglo o como una lista ligada.
return(i);
Supongamos que k es un arreglo den llaves, de k(O) a k(n - 1) y r un arreglo de

390 Estructuras de datos en C


Bsqueda 391
La llave extra insertada al final del arreglo se llama un centinela. el registro es el primero en la tabla, se realiza una sola comparacin; si el registro es
Almacenar una tabla como una lista ligada tiene la ventaja de que el tamao de el ltimo, se necesitan n comparaciones. Si es igualmente probable que el argumento
la tabla puede aumentar la manera dinmica cuando sea necesario. Supongamo_s que aparezca en cualquier posicin dada en la tabla, una bsqueda exitosa hara (en el
la tabla est organizada como una lista lineal ligada apuntada por table Y hgada promedio) (n + 1)/2 comparaciones y, una infructuosa n comparaciones, En cual-
mediante un campo apuntador next. Entonces, suponiendo k, r, key Y rec ~omo an- quier caso, el nmero de comparaciones es O(n).
tes, la bsqueda secuencial con insercin para una lista ligada puede escnbirse de la Sin embargo, es normal el caso en que algunos argumentos se presentan con
siguiente manera: ms frecuencia que otros al algoritmo de bsqueda. Por ejemplo, en los archivos de
la secretara de asuntos escolares de una escuela, es ms probable que se requieran
q = null; los registros de alumnos de ltimo ao que piden sus calificaciones para ingresar a
for (p = table; p != null && k(p) != key; p = next(p)) estudios de postgrado o de alumnos de primer ao a los que se les estn actualizando
q = Pi ss promedios de bachillerato que los registros de alumnos del segundo y tercer aos,
if (p / =. null) I * . lo que significa que k(p) = = KEY * I De111anera similar, es ms probable que los registros de personasque no respetan la
return(p): leYo evasores de impuestos sean recuperadbs de lbs archivos de la oficina de trnsito
* insertar un nuevo nodo */ cide Hacienda que los de un ciudadano que respeta la ley. (Como veremos rhs ade-
s = getnode(.);
lante e(este captulo; estos ejemplos no son realistas dado que es improbable qe
k(s) = key;
sa'sada la bsqueda secuencial en archivos tan largos; perb' :,or el momento, su-
r( s) = rec;
next(s) = null; pongamos que se est usando la bsqueda secuncial.) Entonces, si se colocan al
i f (q == null) principio del archivo los registrns que se accesan con mayor frecuencia, el n'!'ero
table= s; promedio de comparaciones se reduce de manera considerable, ya que los registros
else ms accesados pueden recuperarse en un menor tiempo.
next(q) = s; Sea (pi) la probabilidad de que el registro i sea recuperado. (p(i) es un nmero
return(s); eritre O y l tal que si se hacen m recuperaciones del archivo, m *p(i) seriin de r(i),)
Spongamos tambi~n que p(O) + p(l)+ ... + p(n ~ 1) =' !, de manera que no
La eficienci de l bsqueda en una list puede perfeccionarse mediante la mis- haya posibilidad de que una llave del argumento no se encuentre en la tabla, Enton-
ma tcnica que acabarnos de sugerir para un arreglo: S puede agregar un nodo cen- ces el nmero promedio de comparaciones en la bsqueda del registro es
tinela que contenga la Uve del argumento al finar de la lista antes de ~oi_nen~ar l
bsqueda d manera que la condicin en la iteracinjor sea la cond1c10n simple p(O) +2~ p( 1) + 3 * p(2) + + 1 * p(n - 1)
k(p) ! = key. Sin embargo, el mtodo del centi?ela req~iere de guardar un apunta-
dor externo adicional al ltimo nodo de la hsta. DeJamos al lector los detalles ES drO que, este nmero se mihfmiza si
adicionales-(como;' por ejemplo, qu ocurre cone1 nodo recin agregado cuando se
encuentra la llave dentro de la lista). p(O) >= p( 1) >= p(2) >= >= p(n - l)
La eliminacin de un registro de una tabla almacenada como unarreglo desor-
denado se implanta remplazando el registro a .ser eliminado por el ltimo registro del (Por qu?) As, dado un archivo extenso y estable, el reordenamientodel mismo se-'
arreglo y reduciendo el tamao de la tabla en f. Si el arreglo est ?rdenado de algun_a gn la probabilidad de recuperacin en orden decreciente alcanza un mayor grado
manera (aun si el orden no es segn las llaves), ~ste mtodo no pu~de usarse, Y,<::' de eficiencia cada vez que se busca en el archivo.
promedio la mitad de los elementos en el arreglo tiene que ser_r~cornd~, (Por que,) Una estructura de lista es preferible a un arreglo si se tienen que ejecutar
Si la tabla est almacenada como una lista ligada, es muy ef1c1ente ehmmar un ele- muchas inserciones y'eliminaciones en una tabla. Sin embargo, aun en una lista sera
mento sin tener en cuenta si est o no ordenada, mejor mantener la relacin

Eficiencia de la bsqueda secuencial p(O) >= p( IJ >= >= p(n - 1)

, Cun eficiente es una bsqueda secuencial? Examinemos el nmero de com- para asegurar una bsqueda secuencial eficiente. Esto puede hacerse coh mayor faci-
paractones hechas por una bsqueda secuencial cuando se busca una llave dada, Su- lidad si se inserta en la lista un nuev.o elemento en la posicin que. le corresponde. Si
ponemos que no se realizan inserciones ni elimina:iones, de man_era ~ue estamos prob es la probabilidad de un registro con una llave dada sea el argumento de bs-
buscando en una tabla de tamao constante, n. El numero de comparaciones <lepen: queda, ese registro debera insertarse .entre !_os registros r(i) y r(i + !) donde i es tal
de del lugar de la tabla donde aparece el registro que tiene la llave del argumento. Si que se cumple:

392 Estructuras de datos en C Bsqueda 393


I* No se requiere la transposicin *I
p(i) >= prob >= p(i + l)
return(p);
/* transponer node(q) y node (p). */
Por supuesto, este mtodo implica que se guarda un campo ~xtra P co.~ cada next(q) = next(p};
registro o que p pueda ser computado basndose en alguna otra mformacion del next(p) = q;
(s = null) ? table p next(s) p;
registro.
return (p};
Reordenamiento de una lista para alcanzar mxima eficiencia de
bsqueda Obsrvese que las dos instrucciones if en el algoritmo anterior pueden combi-
Desafortunadamente, es raro que se conozcan de antemano las probabilidades narse dentro de la instruccin simple if(p = = null 11 q = = null) return (p), para
ser ms concisos. Dejamos como ejercicio al lector la implantacin del mtodo de
p(i). Aunque es comn que derto~. ;egistros sea:' .rec~~era~os con ':1ayor frecue~~ia
que otros, es casi imposible identificar con anucipacion dichos registros .. Tambien,, transposicin para un arreglo y el mtodo de moverse-al-frente.
Ambos mtodos estn basados en el fenmeno observado de que un registro
la probabilidad de que un determinado registro sea recuperado puede ca':1biar ~ ~r~-
que ha sido recuperado tiene probabilidad de ser recuperado de nuevo.Adelantando
vs del tiempo. Par.a usar el ejemplo de la escuela dado antes, u~ estudiante imcia.
dichos registros hacia al frente de la tabla, las recuperaciones subsecuentes sern
como alumno de primer ao (con alta probabilidad de que su registro sea rec~pera,.
ms eficientes. La racionalidad del mtodo de moverse-al-frente consiste en que, ya
do) y luego se convierte en alumno de segundo y tercer aos (con poca probab1hd~d
que el registro tiene probabilidad de ser recuperado de nuevo, se coloque en la posi-
de ser recuperado) antes de convrtirse en alumno del ltimo ao (otra ve~ con alta
cin de la tabla desde la cual dicha recuperacin sea ms eficiente. Sin embargo, el
probabilidad de ser recuperado). As, sera de gran ayuda ten:r un algoritmo que
. contraargumento para el mtodo de transposicin es que una sola recuperacin no
reordenara de manera continua la tabla, de tal forma que los registros que se accesan
implica an que el registro ser recuperado con frecuencia; colocndolo al frente de
con mayor frecuencia estuvieran. al frente y los que se accesan con menor frecuencia
\atabla se reduce la eficiencia de la bsqueda para todos los otros registros que antes
al final. . le precedan. Adelantando el registro una sola posicin cada vez que es recuperado;
Hay dos mtodos de bsqueda para realizar lo anterior. Uno de el.los se cono,_e
aseguramos que avance al frente de la lista slo si se recupera con frecuencia.
como mtodo de moverse-al-frente y es eficiente slo en el,aso de una tab.la orgam-:
zada como una lista .. En este mtodo, siempre que una bsqueda es exitosa (es decfr,. Se ha mostrado que el mtodo de transposicin es ms eficiente para un gran
nmero de bsquedas con una distribucin de probabilidad que no cambie. Sin em-
cuando el argumento coincide con la llave de un registro dado), el registro recup~rado.
bargo, el mtodo de moverse-al frente da mejores resultados para un nmero
0

se elimina de su localizacin actual en la lista y se coloca a la.cabeza de la misma ..


pqueo o medio de bsquedas y responde con mayor rapidez. ante un cambio en la
El otro mtodo se llama transposicin, en el cual un registro recuperado se in-
distribucin de probabilidad, Tiene tambin un mejor comportamiento en el peor de
tercambia con el registro que lo precede de manera inmediata. Presentamos un algo,
los casos que el de tratJSposicin. Por esta razn, es preferible el mtodo de moverse-
ritmo para implantar el mtodo de transposicin en una tabla alma':enada en forma
al'frente en la mayora de las situaciones prcticas que involucran la bsqueda
de lista ligada. El algoritmo da como resultado un apuntador al registro recuperado
el apuntador nulo si no se encuentra el registro. Como antes, key es el argum~nto secuencial.
0 Si se requiere un gran nmero de bsquedas con una distribucin de probabili-
de bsqueda, k y r son las tablas de llaves y registros. table es un apuntador al pnmer
dad que no cambie, lo mejor puede ser una estrategia mezclada: usar para las prime-
nodo de la lista. rass bsquedas el mtodo de moverse-al-frente para organizar la Hsta de tranera
rpida en una buena secuencia y luego ca.mbiar al mt.odo de transposicin para
q = s = null /* q se encuentra un paso detrs de Pi *I obtener, un comportamient<;> an mejor. El valor exacto des parn optimizar la.efi-
*
s est dos pasos detrs de p *I cencia global depended.e la longitud de la lista y de la exacta distribucin de proba-
for (p = table; p ! = null && k(p) l.= key; = P = next(p)).
bilidad de acceso.
s ""' q; Una ventaja del mtodo de transposicin sobre el mtodo de moverse-al-frente
q = p; es que se puede aplicar de manera eficiente a tablas almacenadas en forma de arreglo
/* fin de for *f
tanto como a tablas estructuradas como listas. La transposicin de dos elementos en
i f (P == null) un arreglo es una operacin muy eficiente, mientras que mover un elemento de en
return (p);
/* Se ha encontrado el registro en la posicin p. */ medio de un. arreglo al frente impHca (en promedio) mover la mitad del arreglo. (Sin
* Transponer los registros apuntados por p Y q. *I embargo, en este caso el nmero promedio de movimientos no es tan grande, dado
U~===W. . que el elemento que debe ser movido con ms frecuencia, viene de la porcin supe-
* la llave est en la primera-poSicin de la tabla. *I rior del arreglo.)

Estructuras de datos en G 395


394
Bsqueda en una tabla ordenada k
(Llave) '
(Registro)
Si la tabla se almacena en orden ascendente o descendente de las llaves de los 8
registros, pueden usarse varias tcnicas para mejorar la eficiencia de la bsqueda. 14
26
Esto es cierto en especial si la tabla es de tamao fijo. Una ventaja obvia de la bs- 38
queda en un archivo ordenado se tiene cuando la llave del argumento ro est presen- 72
te en el archivo. En el caso de un archivo desordenado, se necesitan n comparaciones 115
Indice
para detectar este hecho. En el caso de un archivo ordenado, suponiendo que las lla- 306
ves argumentos estn distribuidas de manera uniforme sobre el rango. de llaves en el kihdex pindex 321
-~
~

archivo, se necesitan (en promedio) slo n/2 comparaciones. Esto ocurre porque 1 -
321 - 329
sabemos que una llave est faltando en un archivo ordenado de manera ascendente 592 387
tan pronto como encontramos una llave que sea mayor que el argumento. 876 - ,--... I 409 -
' ': -._, - 512
Supngase que es posible reunir un gran nmero de peticiones de recuperacin
antes de que alguna de ellas sea procesada. Por ejemplo, en muchas aplicaciones la. - "- 540
567
respuesta a una peticin de informacin puede diferirse al da siguiente. En tal caso, . -
583
se pueden reunir todas las peticiones de un da especfico y la bsqueda real puede, 592
hacerse durante la noche cuando. no estn entrando nuevas peticiones. Si tanto la
tabla como la lista de peticion~s estn ordenadas, la bsqueda secuencial puede lle.,,
"""'" 602
611 -
-
-

varse a cabo para ambas a la vez. As, no es necesario recorrer la tabla entera para, 618
cada peticin de bsqueda. E.n realidad, si hay muchas de esas peticiones distri, 741 --

buidas uniformemente sobre toda la tabla, cada peticin requerir slo unas cuantas' 798
811 -
consultas (si el.nmero de peticiones es menor que el nmero de entradas de la tabl,a)
o quizils una sola comparacin (si el nmero de peticiones es mayor que el nmero - . . 814
876 -
de entradas de la tabla). En situaciones de ese tipo, la bsqueda secuencial es quizs
el mejor mt.odo a utilizar. ~
A causa de la simplicidad y eficiencia del procesamiento secuencial sobre archi- , Figura 7.1.1 Un arhivo
vos ordenados, podra valer la pena ordenar un archivo antes de buscar llaves en l. ~ secuencial indeXado.

Esto es especialmente cierto en la situacin descrita en el prrafo anterior, donde es-


tamos tratando coa un archivo ~'.principal" y un archivo de"transaccin" extenso;
que contiene las peticiones de bsqueda. del archivo. Suponemos que el archivo est ordenado como un arreglo, que n es el
tamao del archivo y que ndxsze es el tamao del ndice.
La bsqueda secuencial indexada
far (i O; i < indxsize ~& kind~x(i) <= key; i+~)
Hay otra tcnica para perfeccionar la eficiencia de la bsqueda en un archivo
lowlim - (i -- O) ? O :_ pindex( 1 1) ;
ordenado, pero involucra un incremento en la cantidad de espacio requerido. Este
hilim .. = (i .. == indxsize) i n - 1 : pindex(i) -~-- 1;
mtodo se llama mtodo de biisqueda secuencial indexada. Se aparta una tabla auxi' far (j - lowlim; j <- hilim && k(j) != key; j++)
liar, llamada index adems del propio archivo ordenado. Cada elemento en el index
consta de una llave kndex y un apuntador al registro del archivo que corresponde'a' return ((j > hilim) ? -1_ : j);
kndex. Los elementos en el ndice tanto como los elementos en el archivo, tienen
que estar ordenados de acuerdoa las llaves. Si el ndice es un octavo del tamao del
archivo, cada octavo registro del archivo tiene que estar representado en el ndice'. Obsrvese que en el caso de registros mltiples con la misma llave; ~!,algoritmo an-
Esto se ilustra en la figura 7. l. l. terior, no da como resultado un apuntador al primero de tales registros en todos los
El algoritmo usado para buscar en un archivo secuencial indexado es de forma casos.
directa. Sean r, k y key definidas cmo antes, kndex un arreglo de llaves del ndice y L veritaja real del mtodo secuencial indexado es que los elementos.de !atabla
pndex un arreglo de apuntadores dentro del ndice que apuntan a los registros reales pueden examinarse de manera secuencial si todos los registros del archivo tienen que
ser accesados, sin embargo el tiempo de bsqueda de un elemento en particular se

396 Estructuras de datos en C Bsqueda 397


Tabla
secuencial
llave registro
reduce en forma considerable. Se ejecuta una bsqueda secuencial en el ndice, que .
es menor que la tabla. Una vez que se ha encontrado la posicin correcta en el ndice
se ejecuta una segunda bsqueda secuencial sobre una porcin menor de la propia
tabla de registros.
El uso de un indice es aplicable a una tabla ordenada almacenada tanto como
una lista ligada, que como un arreglo. Usar una lista ligada implica una gran sobre-
carga de espacio para apuntadores, aunque las inserciones y eliminaciones pueden
Indice 321
ejecutarse con mucha mayor facilidad. primario
Si la tabla es tan grande que incluso el uso de un ndice no alean.za suficiente
eficiencia (ya sea porque el ndice es extenso con el objetivo de reducir la bsqueda [ndice
321 .
secuencial en la tabla o que el ndice es pequeo de manera que las llaves adyacentes secundario 485
del ndice estn muy alejadas una de otra en la tabla), se puede usar un ndice secun- 591
- .... 591 -

dario. El ndice secundario acta como un ndice al ndice primario que apunta a las

entradas de la tabla secuencial. Esto se ilustra en la figura 7. 1.2. 742 647 -


.r
Las eliminaciones de una tabla secuencial indexada se pueden hacer con mayor
facilidad etiquetando las entradas eliminadas. En la bsqueda secuencial a lo largo -~~ -\_ 706
742
. -.
-._
~ 485

de la tabla, se ignoran las entradas eliminadas. Obsrvese que si se elimina un ele- 1 -


mento, incluso si su llave est en el ndice, nada tiene que hacerse al ndice; slo se .
etiqueta la entrada de la tabla original. re
La insercin en una tabla secuencial indexada es ms difcil, dado que puede .
no haber espacio entre dos entradas de la tabla ya existentes, necesitndose as un 'v>-,
~
corrimiento de un gran nmero de elementos de la tabla. Sin embargo, si ha sido eti-
quetado un elemento cercano en la tabla cuando se elimin, se necesita recorrer slo
'- 591

unos pocos y escribir sobre el elemento eliminado, Esto puede requerir de una altera-
cin del ndice si se recorre un elemento apuntado por un elemento del ndice. Un
mtodo alternativo es mantener un rea de desborde en alguna otra localizacin y
ligarla a cualquier registro insertado. Sin embargo, es\o requerira un campo apun-
tador extra en cada registro de la tabla original. Dejamos como ejercicio la explora- .

cin de esas posibilidades. 1 -, 647 -


_.
'
La bsqueda binaria

El mtodo de bsqueda ms eficiente en una tabla secuencial sin usar ndices o


tablas auxiliares es el de bsqueda binaria. El lector debera estar familiarizado con
esta tcnica de bsqueda de las secciones 3.1 y 3.2. Bsicamente, se compara el argu-
mento con la llave del elemento medio de la tabla. Si son iguales, la bsqueda termi- ' 706 .
na con xito; en caso contrario, se busca de manera Similar en la mitad superior o
inferior de la tabla.
En el captulo 3 observamos que la bsqueda binaria puede definirse de la me- -

jor manera en forma recursiva. Como resultado fueron presentados una definicin
recursiva, un algoritmo recursivo y un programa recursivo para la bsqueda binaria;
Sin embargo, la sobrecarga asociada a la recursividad puede hacerla inapropiada
para su uso en situaciones prcticas en las cuales la eficiencia es una consideracin 742
primordial. En consecuencia, presentamos la siguiente versin no recursiva del algo-
ritmo de bsqueda binaria:
' 1

~
Figura 7.1.2 Uso de un indice secundario 399
398 Estructuras de datos en G
low = O; valor de la etiqueta correspondiente es O, verifquese si la casilla llena previa contiene
h = n - 1.; ,la nave argumento. Si es as, se encontr el elemento, si.no, no existe tal element.o en
while (low <- hi) { la tabla.
mid = (low + hi)/2;
, Par~ insertar un elemento, localcese primero su posicin. Si la posicin est
i f (key -- k(mid))
return(mid); .vacia msertese el elemento en la misma, haga el valor de su etiqueta igual a e
i f (key < k(mid)) igulense los contenidos de todas las posiciones vacas contiguas previas a los conte-
hi = mid - 1.; nidos del elemento lleno previo y todos los contenidos de las posiciones vacas conti-
else 'gu~s siguiente~ al elemento insertado dejando sus etiquetas iguales a O. Si la posicin
low = mid + t.; esta llena, recorranse todos los elementos siguientes una posicin hacia delante hasta
/* fin de wlle *I la.prime~a posicin vaca (escribiendo sobre la primera posicin vaca y poniendo su
return(-1); euquet~ igu.~I a 1) para hacer lugar al nuevo elemento. La eliminacin slo involucra
la locahzacion de una llave y el cambio del valor de su etiqueta asociada a o. Por
;Cada comparacin en la bsqueda binaria reduce el.. nmero de posibles candi- ,supuesto las desventaJas de este. mtodo son el corrimiento que debe hacerse en la
datos. en un factor de 2. As, el nmero mximo de comparaciones de llaves es de ' insercin Yd ':sp~cio limitado para el crecimiento. De manera peridica puede
manera aproximada log2 n. (En realidad, es 2 ldg 2 n dado que en C, se hacen cada ,desearse.redistnbmr los espacios vacos de manera uniforme a lo largo del arreglo
vez dos comparaciones de llaves a travs del ciclo: key = = k(mid) y key < k(mid). para meJorar la velocidad en la insercin.
Sin embargo, en lenguaje ensamblador o .en FORT~AN se hace una sola compara-
cin usando la instruccin aritmtica.IF, (Un compilador que optimice, debera ser Bsqueda por interpolacin
capaz de eliminar la comparncin extra,)As, podemos decir que, el algoritmo de.
bsqueda binaria es o(log n). . Otra. ;cnica para buscar ~n un. arreglo ordenado es la llamada bsqueda por
Obsrvese que la bsqueda binaria se puede usar en conjuncin con la organi- mterpolacwn. ~i las llaves estan distnbmdas de manera uniforme entre k(O) y
zacin secuencial indexada de la tabr \nencionada antes. En lugar de buscar en el k(n - 1), d n_ietodo puede ser aun ms eficiente que la bsqueda binaria.
ndice de manera secuencial, se puede usar una bsqueda binaria. La bsqueda bina- En_pnncipio, como en la bsqueda binaria, low se hace O y high se hacen - .. l,
ria tambin puede usarse al buscar en la tabla principal una vez que dos registros y a .traves del ~lgoritmo, se sabe que la llave argumento key est entre k(low). y
frontera hayan sido identificados. Sin embargo, es probable que el tamao de este k(h1gh). Suponiendo que las llaves estn distribuidas de manera uniforme entre esos
segmento de tabla sea tan pequeo que la bsqueda binaria no sea ms ventajosa dos valores, se esperara que key estuviese en forma aproximada en la posicin
que una bsqueda secuencial.
Por desgracia, el algoritmo de bsqueda binaria slo puede usarse si la tabla es- mid = low + (high -low) ,,. ((key - k(low))/(k(high) -k(low)))
t almacenada como un arreglo. Esto ocurre porque hace uso del hecho de que los
ndics de los elementos delarreglo son enteros consecutivos. Por esta razn, la bs- Si.key s menor que k(mid) haga high igual a mid ~ ; si es mayor; haga /ow igual a
queda binaria es prcticamente intil en situaciones donde, por requerirse muchas m,d + l. Repe11r _el proceso hasta que la llave haya sido encontrada O fow > high.
eliminaciones e inserciones, una estructura de arreglo es inapropiada. ' En efe~to, s1 las ll~ves estn .~istribuidas de manera uniforme a lo largo del
Un mtodo para utilizar la bsqueda binaria en presencia de inserciones y eli- arr~glo, la busqueda por interpolacion requiere un promedio de log 2 (log n) compa,
2
minaciones si se conoce el nmero mXimo de elementos involucra una estructura rac,~nes Yes raro que requiera ~uchas ms, comparado con la bsqueda binaria que
de datos conocida como lista de relleno. El m~fodo usa dos arreglos: un arreglo de requiere log 2 n (de nuevo, considerando las dos comparaciones para igualdad y desi-
elementos y un arreglo de etiquetas paralelo. En principio, el arreglo de elementos gualdad de key Y k(mid) como una). Sin embargo, si las llaves no estn distribuidas
contiene las llaves ordenadas.de la tabla con casillas "vacas" esparcidas de manera de manera uniforme, la bsqueda por interpolacin puede tener un comportamiento
uniforme entre las llaves para permitir el crecimiento de la tabla. Una casilla vaca se prorr,1ed10 ~uy pobre. En el peor de los casos, el valor de mid puede ser de manera
indica mediante un valor O en la etiqueta del elemento del arreglo correspondiente, consistente igual a low + 1 o high - 1, en cuyo caso la bsqueda por interpolacin
mientras que una casilla llena se indica por el valor l. Cada casilla vaca en el arreglo d~ge~era en bsqueda secuencial. En contraste, las comparaciones en la bsqueda
de elementos contiene un valor de llave mayor o igual que el valor de llave de la ca- bmana nunca wn mayores que log 2 n de manera aproximada. En situaciones prcti-
silla llena previa y menor que el valor.de llave de la casilla llena siguiente. As, el c~s, las Hav~s tienden con frecuencia a agruparse en torno a ciertos valores y no es-
arreglo completo est ordenado, y puede ejecutarse en l una bsqueda binaria vlida. tan distribuidas de manera uniforme. Por ejemplo, hay ms nombres que comienzan
Para buscar un elemento ejectese una bsqueda binaria en el arreglo de ele- con
. "S" . que con "Q" ..., Y por, lo .tanto habr ms Surez y menos Quiroz. En
mentos. Si no se encuentra la llave argumento, el elemento no existe en la tabla. Si se snuaciones tales, la busqueda b1nana es muy superior a la de interpolacin.
encuentra y el valor de la etiqueta correspondiente es 1, se localiz el elemento. Si el

400 Estructuras de datos en C Bsqueda


401
Una variacin de la bsqueda por interpolacin, llamada bsqueda por inter-
polacin robusta ( o bsqueda rpida), intenta remediar el pobre comportamiento Implante los algoritmos de bsqueda secuencial y de bsqueda secuencial e insercin
prctico de la bsqueda por interpolacin a la vez que extiende su ventaja sobre la en C para las representaciones con arreglo y con lista ligada.
bsqueda binaria a distribuciones no uniformes de las llaves. Esto se hace estable- 7,1.3, Compare la eficiencia de la bsqueda en una tabla secuencial ordenada de tamafio n y
ciendo un valor gap de manera que mid - low y high - mid sean siempre mayores la bsqueda en una tabla desordenada del mismo tamao para la llave key
que gap. Al inicio, se hace gap igual a sqrt(high - !ow + 1). Probe se hace igual a a. si no est presente un registro con la llave key
low + (high - low) ((key - k(low))!(k(high) - k(low))), y mid se hace iguala b. si hay un registro con la llave key y se busca slo uno
mn(high - gap, max(probe, low + gap)) (donde mn y mx dan como resultado el c. si hay ms de un registro con la llave key y slo se desea encontrar el primero
mnimo y mximo respectivos de dos valores). Es decir, garantizamos que la siguien- d. si hay ms de un registro con la llave key y se desea encontrar a todos
te posicin usada para la comparacin (mid) est como mnimo a gap posiciones de 7, t.4. Asuma que una tabla ordenada se almacena como una lista circular con dos apuntaM
los extremos del intervalo, donde gap es como mnimo la raz cuadrada del interva- dores externos: table y other. table apunta siempre al nodo que contiene el registro
con la menor llave. other es :l principio igual a table pero se hace apuntar al registro
lo. Cuando se encuentra que la llave argumento est restringida al ms peque '
recuperado cada vez que se ejecuta una bsqueda. Si la bsqueda es infructuosa,
de los dos intervalos, de !ow a mid y de mida high, se iguala gap a la raz cuadrada del
other se hace igual a table. Escribir una rutina en C search(table, other, key) que
tamao del nuevo intervalo. Sin embargo, si la llave argumento est en el mayor implante este mtodo y d como resultado un apuntador al registro recuperado o un
de los dos intervalos, se duplica el valor de gap, aunque nunca se permite que.sea apuntador nulo si la bsqueda resulta infructuosa. Explicar cmo manteniendo
mayor que la mitad del tamao del intervalo. Esto garantiza escapar de un agrupa- el apuntador other Pl,lede redu,cir el nmero promedio. de comparaciones -en una
miento extenso de valores de llaves similares. bsqueda.
El nmero esperado de comparaciones para la bsqueda de interpolacin ro- 7.-1.5. Considere una tabla ordenada implantada como un arreglo o como una lista doble-
busta para una distribucin aleatoria de llaves es O(log log n). Esto es superior a la mente ligada de manera que se pueda hacer una bsqueda secuencial en la tabla hacia
bsq,ueda binaria. En una lista de aproximadamente 40 000 nombres, la bsqueda delante o hacia atrs. Suponer que un solo apuntador p apunta al ltimo registro re-
binaria requiere un promedio en forma aproximada de 16 comparaciones de llaves; , cuperado de manera exitosa. La bsqueda comienza siempre en el registro apuntado
A causa del agrupamiento de los nombres en situaciones prcticas, la bsqueda por por p pero puede proseguir en cualquier direccin. Escribir una rutina search(table,
interpolacin requiere de 134 comparaciones promedio en un experimento real, p,-key) para el caso de_ un arreglo y una li~ta doblemente ligada que.recupere un
mientras que la bsqueda por interpolacin robusta requiere slo 12.5. En una lista registro con llave key y modifiqe p de acuerdo a ello. Demostrar que las compara-
distribuida de manera uniforme de aproximadamente 40 000 elementos -logz (log2 ciol).es de llaves en ambos casos, si se localiza o no la llave, son las mismas que en el
caso del ejercicio previo, en el cual-la _tabla puede ser e_,camin_ada en una sola: direccin
40 000) es alrededor de 3.9- la bsqueda por interpolacin robusta requiere 6.7
pero el proceso de examen puede comenzar en un d dos puntos.
comparaciones promedio. (Debe observarse que el tiempo de computacin extra re-
7.1.6. Considere.un programador que escribe el.siguiente programa:
querido por la bsqueda por interpolacin robusta puede ser sustancial pero se igno-
ra en estos hallazgos.) El peor caso para la bsqueda por interpolacin robusta es if ( C1)
( O(log n )2) comparaciones, que es ms grande que para la bsqueda binaria, pero if ( c 2 )
mucho mejor que el O(n) de la bsqueda de interpolacin normal. if ( C3)
Sin embargo, en la mayora de las computadoras, los clculos requeridos por
la bsqueda por interpolacin son muy lentas, dado que involucran aritmtica con if (en)
las llaves y multiplicaciones y divisiones complicadas. La bsqueda binaria slo re- enunciado
quiere aritmtica en.ndices enteros y divisin entre 2 que puede ejecutarse de mane-
ra eficiente recorriendo un bit a la derecha. As, los requerimientos computacionales donde e es una condicin que puede ser verdadera o falsa. Note qe la reubicacin de
de la bsqueda por interpolacin, ocasionan con frecuencia que sta se ejecute ms las condiciones en orden diferente da como resultado un Programa equivalente, dado
despacio que la bsqueda binaria aun cuando la primera requiere menos compara- qe [instruccin] slo se ejecuta si todas las e; son Verdaderas. Asumir que time(i) es
ciones. el tiempo necesario para evaluar la condicin e y que prob(i) es la probabilidad de
que e sea verdadera. En qu orden deberan ser redispuestas las condiciones para
hacer el programa ms eficiente?
EJERCICIOS 7.1.7. Modifique la bsqueda secuencial indexada de manera que d como rsultado el pri-
mer registro de la tabla en caso de que haya varios con la misma llave.
7.1. t. Modifique los algoritmos de bsqueda e insercin de esta seccin de manera que se 7.t.s. Considere la sigui~nte impl_.IltaC_i6n enc de un archivo secuencial indexado:
conviertan en algoritmos de actualizacin. Si un algoritmo encuentra un i tal que key
sea igual a k(i), cambiar e! valor de r(i) por rec. #define INDXSIZE 100;
#define TABLESIZE 1000;

402 Estructuras de datos en C


Bsqueda. 403
struct indxtype far (j 1; fib(j) < n; j++)
int kindex;
int pindex; mid - n - fib(j - 2) + 1;
}; f1 fib(j - 2);
struct tabletype f2 fib(j - 3);
int k while (key ! k(mid))
int r;' i f (mid < O key > k(mid)) {
int flag; i f ( f1 -- 1)
}; return(-1);
struct isfiletype mid += f2;
struct indxtype indx[INDXSIZEJ; f1 f2;
struct tabletype table[TABLESIZEJ; f2 ~- f1;
}; }
struct isfiletype isfile; else {
. i f (,f2 O.)
Escriba' una rutina en C, create(isfile) que iniialice un archivo tal de datos de entf return(-1);
1111.d '-= f2;
da. Cada lnea de entrada contiene una llave y un registro. La entrada-est ordena
tf1-f2;
de acuerdo a. las llaves en orden ascendente." Cada entrada de ndice correspondi
f1 f2;
diez entradas de la tabla, flag se hace igual a. VERDADERO en una entrada de
f2 t;
tabla que est ocupada e igual a FALSO en una que est desocupada. Dos de cad,i
; lindeil *I
diez entradas de la tabla se dejan desocupadas para permitir el crecirriiento futuro.
return( mi'd);
7.1.9. Dado un archiv secuencial indexado comben el ejercicio previo, escriba una rutin~('
er C si!drch(isfile, key) p_ara imprimir el registro en el archi\lo con la Ifavekey si hayJ
uno y una indicacin de que no lo hy si no existe Un-registro con dicha llave. (Cffi<:'.t Explique cmo" trabaja Ste algritino. Confrontar el nmero de comparacines de
llaVes cdn el nnier'U.-rlii: la bsqtieda binria. Modificar la porcin inicial de
puede asegurar que una bsueda infructuosa es tah~ eficiente como sea posible?)
Tambin, escriba rutins insert(lsfile, key-,- rf!c) parainSeifar _un. registrO-reC con llave? este algoritmo de manera que calule los nmeros deFlbonacci de manera eficiente,
key y dele/e (isfile, key) para eliminar un registro con llave kejl. en-lllgar de buscarlos en un~nabla o cmputarlos cada-vez de nuevo.
7.1.10. Considere la siguiente" versin de la bsqueda binaria, qlle supone Que las llaves eStI?- 7.-1.-12. Modifique l bsqueda binaria del texto de,maner"que en el cilso de una bsqueda
contenidas en k(l) a k(n) y que el elemento especial k(O) es menor que cualquier llave infructuosa, d como resultado el ndice i tal que k(i). < key < k(i + 1). Si key <
posible: k(O), d como resultado.- 1 y si key > k(n - 1), dan - . l. Hacer lo mismo para los
mtodos de bsqueda de .los ejercicios 7.1.10 y 7: 1.11:
mid n/2;
len (n -
),)/2;
while (key != k(mid)) BUSQUEDA EN ARBOLES
i f (key < k(mid))
mid len/2;
En a seccin 7 .1 discutimos operaciones de bsqueda en un ~rchivo que estaba
else
mid += len/2;
organizado bien como un arreglo o como una lista. En esta seccin consideramos
i f ( len ,,,. O) varias maneras de organizar archivos como rboles y lgunos algoritmos de bs-
return(-); queda asociados. .
len!= 2; En las secciones 5.1 y 6.3 presentamos un mtodo de usar ut;t rbol binario pa'
/* fin dewhile *I ra almacenar un archivo con el objetivo de hacer ms eficiente el ordenamiento del
return(mid) archivo. En ese mtodo, todos los descendientes izquierdos de un nodo con llave key
tenan llave menor que key y todos los descendientes derechos tenan llaves mayores
Demuestre que es.te algoritmo es correcto. Cules son las ventajas y/o desventajas de o iguales que key. El recorrido en orden de tal rbol binario, produca el archivo en
este mtodo Con respecto a1 mtodo presentado Cn el texto? orden ascendente de las llaves.
7. 1.11. El siguiente algoritmo de bsqueda en un arreglo ordenado que se conoce como bS- Un rbol as, puede usarse como un rbol de bsqueda binaria. Usando nota-
queda de Fibonacci a causa del uso de los nmeros d Fibonacci. (Para Una definicin cin de rbol binario, el algoritmo para la bsqueda de la llave key en un rbol de
de los nmeros de Fibonacci y de la funcin/ib ver la seccin 3.1.) ese tipo es como sigue (suponemos que cada nodo contiene cuatro campos: k, que

404 Estructuras de datos en C Bsqueda 405


30 a[OJ
guarda el valor de la llave del registro, r, que guarda el propio registro y eft y righi
47 a[IJ
que son apuntadores a los subrboles):
86 a[21
p = tree; 95 a[l]
while (p != null && key != k(p))
p = (key < k(p)) ? left(p) : right(p); 115 a[4]
return(p); a[S]
130
138 a[6]
La eficiencia del proceso de bsqueda puede perfeccionarse usando un centine-.
la como en la bsqueda secuencial. Un nodo centinela con un apuntador externo por 159 a[7]
separado apuntndole, permanece asignado con el rbol. Todos los apuntadores a[ 166 a[8]
rbol !efl o righl que no apunten a otro nodo del rbol apuntan ahora a ese centinela a[9]
t84
en lugar de ser iguales al apuntador nulo. Cuando se ejecuta una bsqueda, se inser'
ta primero la llave argumento en el nodo centinela, garantizando as que ser 206 a[IO]
encontrada en el rbol. Esto permite que el encabezamiento del ciclo de bsqueda ' 212 a[t IJ
escriba como whi!e (key! = k(p)) sin el riesgo de un ciclq infinito. Tras abandonar el/
219 a[t2]
ciclo, si pes.igual al apuntador externo al centinela, la bsqueda fue infructuosa; en '
caso contrario p apunta al nodo deseado. Dejamos al lector el algoritmo real. 224 a[tl]
Obsrvese que la bsqueda binaria de la seccin 7. l en realidad usa un arreglo 237 a[t4]
ordenado como un rbol de bsqueda binaria implcito. , El elemento medio del
258 a[tSJ
arreglo puede pensarse como la raz del rbol, la mitad inferior del arreglo (cuyos
elementos .so.n menores que, el elemento medio) puede considerarse el subrbol iz- 296 a[l6)
quierdo y la. mitad superior (cuyos elementos son rnay.ores que e.l elemento. medio) a[17l
puede considerarse el subrbol derecho.
a[t8]
Un arreglo ordenado puede producirse a partir de un rbol de bsqueda bina-
ria recorriendo el rbol .en orden e insertando cada elemento en forma secuencial al9]
dentro del arreglo al ser visitado. Por otra parte, hay muchos rboles de bsqueda
binaria que corresponden ai'un arreglo ordenado dado: Viendo el elemento medio
del arreglo como la raz y los elementos restantes como subrboles izquierdo y de-
recho de manera recursiva, se llega a un rbol de bsqueda binaria relativamente
balanceado (ver la figura 7.2. la). Viendo el primer elemento del arreglo como la raz
de un rbol y cada elemento sucesivo como el hijo derecho de su antecesor resulta.urr
rbol binario muy desbalanceado (vea figura 7.2.1 b).
La ventaja de usar un r.bo.l de bsquedabinaria sobre el uso de un arreglo es
que un rbol p,ermie que' las operaciones de insercin, elimina.cin y bsqueda se
ej~cu1en co.n efic_i.fncia. S_i_ se .s~un arreglo, una inserc;in o eliminacin requieren
mover casi la mitad de los elementos del mismo. (Por qu?) Por otra parte, la
insercin o elimin.acin en un rbol de bsqueda requieren de .s.lo unos cuantos
ajustes de apuntaaores. . . .

Insercin en un rbol de bsqueda binaria


(a}
El siguiente algoritmo realiza una bsqueda en un rbol de bsqueda binaria e
inserta un .nuevo registro si la bsqueda resulta infructuosa. (Suponemos la existen- Figura 7.2.1 Un arreglo .ordenado y dos. de. sus representaciones
cia de una funcin mqkeuee que construye un rbol binario consistente en un solo con rboles binarios.
nodo. cuyo campo de informacin se transfiere como argumento y da como resulta-
Bsqueda 407
406 Estructuras de datos en C
do un apuntador al rbol. Esta funcin se describi en la seccin 5.1. Sin embargo,
en nuestra versin particular, suponemos que maketree acepta dos argumentos, un
registro y una llave.)

q = null;
p = tree;
while (p != null) {
i f (key == k(p))
return(p);
g = p;
i f (key < k(p))
p left(p);
else
p right(p);
/* fin de while */
v = maketree'( rec, key) ;-
i f ( g == n u11)
tree = v;
else
i f (key < k(g))
left( q) = v;
else
right(g) = v;
return( v);

. Obsrvese que despus de insertar un nuevo registro, el rbol conserva la propiedad


de estar ordenado en un recorrido en orden.

Eliminacin de un rbol de bsqueda binaria

Presentamos ahora un algoritmo para eliminar un nodo con llave key de un r-


bol.de bsqueda binaria. Hay tres casos a considerar. Si el nodo a ser eliminado no
tiene hijos, puede eliminarse sin ajustes posteriores al rbol. Esto se ilustra en la
figura 7 .2.2a. Si el nodo a ser eliminado tiene slo un subrbol, su nico hijo puede
moverse hacia arriba y ocupar su lugar. Esto se ilustra en la figura 7.2.2b. Sin
embargo, si el nodo p a ser eliminado tiene dos subrboles, su sucesor en orden s ( o
predecesor) debe tomar su lugar. El sucesor en orden no puede tener un subrbol iz-
quierdo (dado que un descendiente izquierdo sera el sucesor en orden de p). As, el
hijo derecho des puede moverse hacia arriba para ocupar el lugar s, Esto se ilustra
en la figura 7.2.2c, donde el nodo con llave 12 remplaza al nodo con llave 11 y es
remplazado, a su vez, por el nodo. con llave. 13.
En el siguiente algoritmo, si no existe nodo con llave key en el rbol, el rbol se
deja intacto.

p = tree;
(b) q = null;
I* buscar el nodo con la llave key, apuntar dicho nodo *I
Figura 7 .2.1 (Con/.) I* con p y se.alar a su padre con q, si existe *I

Estructuras de datos en C .Bsqueda 409


408
(a) Eliminacin del nodo con llave 15, . (e) Eliminacin del nodo con llave lL

Figura 7 .2.2 (C~nt.)

/* asignar . l variable rp el nodo que reemplzar */


;, a nade (p) . *I
/* Los primeros doS casos: el nodo que se ialiminar, lo ms */
/* tiene un hijo */
i f (left(p) == null)
rp = right(p);
else
i f (right(p) == null)
rp = left(p);
else {
I* Tercer caso: node (p) tiene do_S _hij_s. _Asi(Jnar a rp *I
!* al sucesor in_order de p y" a f el padre de rp *I
f = p;
rp = right(p);
s = left(rp); !* s eS siempre el hi]o izquirdo de rp *I
(b) Eliminacin del nodo conJla.ve 5. while ( s != n ulI)
f = rp;
Figura 7.2.2 Eliminacin de nodos' de un rbol di:'} bsqueda rp =- s;
. binaria. s = )eft( rp);
/* fin dewhile *I
!* , ;. en este pup.to, rp es el sucesor inorder.- de p *I
f ( f 11= P) {
while (p != null && k(p) != key) { /* p no es el padre de rp y rp =,;,
left(f) *I
q = p; left(f) right(rp);
p = (key < k(p)) ? left(p) : right(p); /* eliminar node(rp) de SU: posiciri ctul y *I
I* fin de while */ /* remplazarlO on el hijo del'echci de nOde (rp) *I
i f (p == null) /* nade (rp) toma ellugr de. nde(p) *I

I* la llave no est en el rbol *! right(rp) = right(p);


!* dejar el rbl sin modificar *I /.* fin-de if */
return I* asignar- al hijo izquierdo de node(rp )- un -valor. tal

410 Estructuras de datos en C Bsqueda 411


* que node(rp) lome el lugar de node(p) *I mento de bsqueda, y sea in la longitud interna de camino promedio de un rbol de
left(rp) = left(p); bsqueda binaria aleatorio den nodos. Entonces sn es igual a Un + n)ln.
I* fin de if */ Sea un el nmero de comparaciones promedio requerido por una bsqueda
I* insertar node(rp) en la posicin antes *I infructuosa en un rbol de bsqueda binaria aleatorio de n nodos. Hay n + l mane-
!* ocupada por node(p) *I
ras posibles en que puede ocurrir una bsqueda infructuosa de la llave key: key es
if ( q == n u 11)
menor que k(l), key est entre k(l) y k(2), ... key est entre k(n - 1) y k(n) y key
/* node(p) era la raz del rbol *I
tree = -rp; es mayor que k(n). Ellas corresponden a los n + 1 apuntadores nulos a subrboles
else en un rbol de bsqueda binaria den nodos. (Se puede mostrar que cualquier rbol
(p == left(q)) ? left(q) rp right( q) rp; de bsqueda binaria con n nodos tienen + 1 apuntadores nulos.)
freenode(p); Considrese la extensin de un r.bol binario formado remplazando cada apun:
return; tador izquierdo o derecho nulo por un apuntador a un nuevo nodo hoja aparte, lla-
mado un nodo externo. La extensin de un rbol binario de n nodos tiene n + 1
nodos externos, cada uno correspondiendo a uno de los n + 1 rangos de llave para
Eficiencia de las operaciones con rboles de bsqueda binaria na bsqueda infructuosa. Por ejempo, el rbol inicial de la figura 7.2.2 contiene
13 nodos. Su extensin agregara dos nodos externos como hijos de cada una de las
Como ya vimos en la seccin 6;3 (ver figuras 6.3. l y 6.3.2), el tiempo requerido hojas que contienen I, 7, 10, 13 y 15 y un nodo externo a las.que contienen 5, 6, 9 y
para buscar en un rbol de bsqueda binaria vada entre O(n) y O(log n), dependiendo IZpara _un total de 14 nodos externos. Se define la longitud externa de camino, e, de
de la estructura del rbol. Si los elementos se insertan en el rbol usando el algoritmo un rbol biriario como la suma de los niveles de todos los nodos externos de su exten-
de insercin anterior, la. estructura del rbo.l depende del orden en que se inserten los sin. La extensin del rbol inicial de la figura 7,2;2 tiene 4 nodos externos ew el nis
registros. Si los registros se insrtan en .orden (u orden inverso), el rbol resultante ve! 3, 6 en el 4 y 4 en el 5, para una longitud externa de camino de 56. Obsrvese que
contiene todas las ligaduras izquierdas (o derechas) nulas, de manera que la bs- el nivel de un nodo externo es igual al nmero de comparaciones llevadas a cabo en
queda en el rbol se reduce a una bsqueda secuencial. Sin embargo, si los registros una bsqueda infructuosa de la llave en el rango representado por ese nodo externo.
se insertan de manera que la mitad de los que quedan despus de un registro dado r Entonces si e,, es la longitud externa de camino promedio de un rbol de bsqueda
con llave k tiene llaves menores que k y la mitad tienen llaves mayores que k, se al- binaria aleatorio den nodos; Un= enl(n + 1). (Con ello se supone que cada uno de
canza un rbol balanceado en el cual son suficientes de manera aproximada log los n + 1 rangos de llave tiene igual probabilidad en una bsqueda. infructuosa, En
n comparaciones de llave para recuperar un elemento. (De nuevo; debe observarse fa figura 7 .2.2 el nmero promedio de comparaciones para una bsqueda infruc-
que el examen de un nodo en nuestro algoritmo de insercin requiere dos compara- tuosa es 56/14 o 4.0.) Sin embargo, se puede mostrar qee = i+2n para cualquier
ciones; una para igualdad y otrn para menor que. Sin embargo, en lenguaje de rbol binario den nodos (por ejemplo; en la figura 7,2.2, 56 = 30 + 2 * 13); de ma-
mquina y con algunos compiladores, ambas pueden combinarse en una sola.) nera quee n =in+ 2n. Comosn =(un+ n)!n Y Uh = e/(n + 1), esto significa
Si los registros se presentan en forma aleatoria (es decir, cualquier permuta- que s,, = ((n + 1)/n) un-1.
cin de los n-elementos tiene la misma probabilidad) es ms frecuente que resulten El nmero de comparaciones requeridas para accesar una llave es uno ms que
rboles balanceados que rboles que no lo son, de manera que, en promedio, el ef'11mero requerido cuando el nodo fue insertado. Pero el nmero requerido para
tiempo de bsqueda permanece en O(log n). Para ver esto, definamos la longitud insertar una llave es igual al nmero requerido en una bsqueda infructuosa de esa
interna de camino, i, de un rbol binario como la suma de los niveles de todos los llave antes de que fuese insertada. As, Sn = 1 + (u 0 + u 1 + ... + Un_ 1)!n. (Es de-
nodos del rbol (obsrvese que el nivel de un riodo es la longitud del camino de la cir; el nmero promedio de comparaciones para recuperar un elemento en un rbol
raz al nodo). En el rbol inicial de la figura 7.2.2, por ejemplo, i es igual a 30 (1 nodo de nnodos es igual al nmero promedio en accesar cada uno de los.primeros elemen-
en el nivel O, 2 en el nivel 1, 4 en el 2, 4 en el 3 y 2 en el 4: 1 * O + 2 1 + 4 2 + 4 tos hasta el n-simo, y el nmero para accesar el i-simo es igual a uno ms que el
3 + 2 4 = 30). Como el nmero ele comparaciones requeridas para accesar un nnro para insertar el i-simo o 1 + u;_ 1.) Combinando esto con la ecuacin
nodo en un rbol de bsqueda binaria es uno ms que el nivel del nodo, el nmero s,, = ((n + l)!n)un-1 se obtiene
promedio de comparaciones requeridas para una bsqueda exitosa en un rbol de
bsqueda binaria co r, .nodos es}gual a{i + n)ln, suponiendo igual probabilidad (n+ l)u,, = 2n-+- uo + u + + u,,_
1 1
de acceso para todo nodo del rbol. As, para el rbol inicial de la figura 7 .2.2, (30
+ 13)/13, o en forma aproximada 3.31, ser. el nmero de comparaciones requeri- para toda n. Remplazando n por n - 1 tenemos
das para una bsqueda exitosa. Seas n igual al nmero promedio de comparaciones
requeridas por una bsqueda exitosa en un rbol de bsqueda binaria aleatorio den nu;,_, = 2(n - 1) + u0 + u + + u,.~ 2
nodos en el cual hay igual probabilidad de que cualquiera de las n llaves sea el argu-

412 Estructuras de datos en C Bsqueda 413


y restando de la ecuacin previa tenemos
(n .+ l)u 11 - lllln-1 = 2 t U11 -t por su hijo slo cuando el predecesor o sucesor en orden respectivamente no estn
contenidos en su subrbol), produce al final mejores rboles que los aleatorios des-
o pus de mezclar inserciones y eliminaciones adicionales. Datos empricos indican
u,,= u,,- 1 + 2/(n + 1) que se reduce la longitud de camino despus de muchas inserciones y eliminaciones
simtricas alternas en 88 por ciento de su valor aleatorio correspondiente en forma
Como u 1 1 tenemos que
. aproximada.
u,, = 1 + 2/3 + 2/4 + + 2/(n + 1)
Eficiencia de rboles de bsqueda binaria no uniforme
y, por lo tanto, coTTJo s,, = ((n + l)ln)u,,-1, qu
Todo lo anterior supone que hay igual probabilidad para cualquier llave en la
s,, = 2((n +.l)in)(I +.l/2 + 1/3 + + l/n) ~3..
tabla corno argumento de bsqueda. Sin embargo; en la prctica real es comn que
Cuando n se hace grande, (n, + 1)/n es de manera aproximada 1 ; se pued~ se recuperen algunos registros con mayor frecuencia, otros con frecuencia moderada
mo~trarque 1 + 1/2_ + ... + 1/n es en forma aproximad log (n), donde log (n) se y otros casi nunca. Supngase que los registros se insertan en el rbol de manera que
define corno el logaritmo natural de n .en el archivo de la biblioteca estndar d~ c los accesados con mayor frecuencia preceden a los accesados con menor frecuencia.
math.h. As, s n puede ser aproximada (paran grande) por. 2 * log (n), que es igual a Entonces los registros recuperados con mayor frecuencia estarn ms cerca de la raz
1.386 * log 2 n. Esto significa queel tiempo de bsqueda prornedio"en un rbol de del rbol, de manera que el tiempo promedio de bsqueda exitosa se reduce. (Por su-
bsqueda binaria aleatorio es O(logn) y requiere slo 39% ms comparaciones, e; pesto, esto supone que el reordenarniento de las llaves por frecuencia reducida de
promedio, que en un rbol binario balance_ado.. . . . accesos no produce un rbol binario muy desbalanceado, dado que si lo hace, la ven-
Como ya observamos; la ins.ercin en.un rbol de bsqueda binaria requiere el taja por la reduccin en el nmero de comparaciones para los registros accesados
mismo nmero de comparaciones que el de una bsqueda infructuosa de la llave. La con mayor frecuencia podra no compensar la desventaja de incrementarse el nme-
eliminacin requiere el mismo nmero de comparaciones que Ja bsqueda de la llave ro de comparaciones para la vasta mayora de los registros.)
a ser eliminada, aunque involucra trabajo adicional para encontrar el sucesor o ante- Si los elementos a ser recuperados forman un conjunto constante, sin inser-
cesor en .orden. Puede mostrarse que el algoritmo de eliminacin que presentamos ciones o eliminaciones, puede valer la pena definir un rbol de bsqueda binaria que
en realidad modifica el costo de bsqueda promedio subsecuente en el _rbol. Es de- haga ms eficientes las bsquedas subsecuentes. Por ejemplo, considerar los rboles
cir;. un rbol aleatorio de .n-llaves creado insertando n + 1 llaves y eliminando des, de bsqueda binaria de la figura 7.2.3. Ambos rboles: el de la figura 7 .2.3a y el de
pus una llave aleatoria tiene una longitud i.nterna de camino inferior (y por lo tanto la 7.2.3b_ contienen t.res elementos kl, k2 y k3, donde k_l < k2 < k3, y son rboles
menor costo d_e bsqueda promedio) a la de un rbol aleatorio den-llaves creado in- de bsqueda binaria vlidos para ese conjunto. Sin embargo, la recuperacin de k3
sertando n llaves. El proceso de eliminacin de un nodo con un hijo remplazando a\ requiere:dos comparaciones en la figura7.2.3a pero slo una en.la figura 7 .2.3b. Por
primero por el ltimo, sin importar si _el hijo es izquierdo o derecho, produce un supuesto, existen an otros rboles de bsqueda binaria vli,dos para este conjunto
rbol mejor que el promedio; un algoritmo de eliminacin similar que sio remplace de llaves.
un nodo con un hijo por su hijo si este ltimo es un hijo izquierdo (es decir, si su El nmero de comparaciones de llaves necesario para recuperar un registro es
sucesor no est contenido en su. subrbol) y en caso contrario. remplace el nodo por igllal al nivel de ese registro en el rbol de bsqueda binaria ms I. As, la recupera-
su sucesor de en orden produce un rbol aleat.orio, suponiendo que no se llevan a cin de/,2 requiere una comparacin en el rbol dla,figura 7 .2.3a. pero requiere
cabo inserciones adicionales. Este ltimo algoritmo se llama algoritmo de elimin.a- tres comparaciones en el rbol de la figura 7.2.3b. Una bsqueda binaria infrucs
cin asifntrica, - . ., tuosa de un argumento que est de manera inmediata entre dos llaves a y b requiere
Sin embargo, de rrianerabastante extraa, cuando se hacen inserciones y elim_i- tantas comparaciones de llaves como el nmero mximo de comparaciones requeri-
naciones adicionales usando el algoritmo de_ elirninaci_n asimtrica; la longitud das por una bsqueda exitosa para a o b. (Por qu?) Esto es igual a I ms el mximo
interna de camino y el tiempo de bsqueda disminuyen al inicio pero empiezan luego de los niveies de a o b. Por ejemplo, una bsqueda de la llave que est entre k2 y k3
a elevarse en forma rpida otra vez. Para rboles que contengan ms de 128 llaves, requiere dos comparaciones de llaves en la figura 7.2:3a y tres comparaciones en la
la longitud interna de camino se vuelve a fin de cuentas peor que para un rbol figura 7.2:3b, mientras queuna bsqueda de una llave mayor de k3 requiere dos
aleatorio, Y para rboles con ms de 2048 llaves, la_ longitud interna de camino se comparaciones en la figura 7.2.3a pero slo una en la figura 7 .2.3b.
vuelve al final 50 por ciento peor que para un rbol aleatorio. Suponer que p 1, p2 y p3 son las probabilidades de que el argumento de bs-
Un algoritmo de eliminacin simtrica alternativo, que alterne la eliminacin queda sea igual a k 1, k2 y k3 en forma respectiva. Suponer tambin que qO es la pro-
del predecesor y sucesor en orden (pero que siga remplazando un nodo con uo hijo babilidad de que el argumento de bsqueda sea menor que k 1, q I es la probabilidad
de que est entre k I y k2, q2 de que est entre k2 y k3 y q3 de que sea mayor que k3.
Entonces pi + p2 + p3 + qO + ql + q2 + q3 = l. El nmero esperado de com-
paraciones en una bsqueda es la suma de las probabilidades de que el argumento
414 Estructuras de datos en C
Bsqueda 415
qO = .1 qO = .1
ql = .2 ql = .1
q2 = .1 q2 = .1
q3 = .1 q3 =' .2
K3

Nmero esperado para 7.2.3a = 1.7 Nmero esperado para 7.2.3a = 1.9
(a) Nmero esperado de comparaciones.
2pl +p2+2p3 +2qQ+2ql +2q2+2q3 Nmero esperado para 7 .2.3b = 2.4 Nmero esperado para 7.2.3b = 1.8

Arboles de bsqueda ptimos

Un rbol de bsqueda binaria que minimice el nmero esperado de compara-


ciones para un conjunto dado de llaves y probabilidades se llama ptimo. El ms
rpido algoritmo conocido para producir un rbol de bsqueda binaria ptimo
Kl es O(n 2) en el caso general. Esto es muy costoso a menos que el rbol se mntenga
sin cambiar a lo largo de uOnmero muy grande de bsquedas. En casos en los
cuales todas las p(i) sean iguales a O (es decir, las llaves actan slo para definir va-
fores de rangos con los que esin asociados los datos, de manera que todas las
bsquedas sean "infructuosas"), existe un algoritmo O(n) para crear un rbol
de bsqueda binaria ptimo.
Sin embargo, aunque no existe un algoritmo eficiente para construir un rbol
(b) Nmero esperado de comparaciones. Figura 7.-2.3 pos rboles de ptimo en el caso general, hay varios mtodos de construccin de rboles cercanos a
2p 1 + 3p2 + p3 + 2q0 + 3q 1 + 3q2 + q3 bsqueda binaria. ptimos en tiempo O(n). Suponer n llaves, de k(l) a k(n). Seap(i) la probabilidad
de buscar la llave k(i), y q(i) la probabilidad'de una bsqueda infructuosa entre
k(i - 1) y k(i) (con q(O) la probabilidad d una bsqueda infructuosa de una llave
tenga m valor dado por el nmero de comparaciones requeridas 'parn recuperar ese menor que k(l), y q(n) la probabilidad de una bsqueda infructuosa de una llave
valor donde la suma se toma sobre todos los valores que sean' posibles argumentos posterior a k(n)). Definir s(i,j) como q(i) + 'p(i + 1) + :,. + qU).
de bsqueda. Por ejemplo, el nmero esperado de comparaciones en una bsqueda Un mtodo Hmado el mtodo de balanceo, intenta encontrar un valor i que
eri el rbol de la figura, 7 .2.3a es minimice el valor absoluto de s(O, i - 1) -s(i, n) y establezca a k(i) como la raz
. del rbol de bsqueda binaria, de k(I) a k(i - 1) en su subrbol izquierdo y de k(i
2pl + p2 +2p3 + 2q0 + 2ql + 2q2 + 2q3 + 1) a k(n) en su subrbol derecho. El proceso se aplica entonces'de manera recursiva
para construir los subrboles izquierd y derecho.
y el .nmero esperado de corhpatciones en mia bsqueda en elrbol de la figura La localizacin del valor de i para el cual abs(s(O, i - 1) -s(i, n)) se hace
7.2.3b es mnimo, puede realizarse de manera eficiente como sigue. Primero, definir un
arreglo sO[n + l] de manera que sO[il sea igual a s(O, i). Esto puede hacerse iniciali-
2pl + 3p'.2 + p3 + 2q0 + 3ql +3q:l + q3 zahdosO[O] conio q(O) y sOU] comosOU - !] + PU) + qU) para} de 1 a n. Una vez
inicializado sO, se puede calcular s(i, j) para toda i y f como
Este nm.ero esperado d~ comparaciones puede usarse como una medida de cun sOU] -sO[i - 1] -p(i) siempre que sea necesario. Definimos siU) como s(O,
"bueno" es un rbol de bsqueda binaria en particular para un conjunto dado de J - 1) -sU, n). Queremos minimizar abs(si(i));
llaves y un conjunto dado de probabilidades. As, para las siguientes prob~bil!dades Tras inicializar sO, comenzamos; el proceso de hallar un i para minimizar
a la izquierda, el rbol de la figura 7 .2.3a es ms eficiente; para las probabfdades abs(s(O, i - 1) :_:,,(i, n))', o si(i). Obsrvese que si es una funcin montona cre-
de la derecha; es ms eficiente el rbol de la figura 7 .2.3b .. ciente. Obsrvese tambin que si(O) = q(O) -1, que es negativo, y si(n) = 1 -'q(n
+ l) que es positivo. Revisar los valores de si(l), :Si(n), si(2), si(n - l), si(4),
pi = ,! pl .l si(n - 3), ... , si(2J), si(n + I-' 21) por turno hasta descubrir el primer si(21) positi-
p2 = .3 p2 = .1 vo o el primer si(n + l - 2l)negativo. Si se encuentra primero un si(2J) positivo, el i
p3 = .1 p3 = .3 deseado que minimiza abs(si(i)) est dentro del intervalo [21- 1, 21]; si,se encuentra

416 Estructuras de datos en C Bsqueda 417


1 d
decir, la llave k tal que mismo nmero de llaves en el subrbol sean menores que, y
primero un si(n + 1 - 2j) negativo, el i deseado est en el intervalo [n + 1_- 2 , 1n mayores que k). Esto tiene la doble ventaja de garantizar un rbol balanceado y ase-
1
+ - 2j-1]. En cualquier caso, i ha sido limitado a un intervalo de tamano 2 - . gurar que las llaves ms frecuentes se encuentren cerca de la raz. Aunque los rboles
Dentro del intervalo, usar una bsqueda binaria para limitar a i. El efecto de dupli- de divisin por medianas requieren una llave extra en cada nodo, pueden construirse
car el tamao del interv.alo garantiza que el proceso recursivo .entero sea O(n), como rboles binarios casi completos implantados dentro de un arrreglo, ahorrando
mientras que si para comenzar se usara uua bsqueda binaria en todo el intervalo [O, el espacio de los apuntadores de rbol. Un rbol de divisin por medianas de un con-
n], el proceso sera O(n lag n). . . junto dado de llaves y frecuencias puede construirse en tiempo O(n log n), y una
Un segundo mtodo usado para construir rboles de bsqu:da ?mana cerca- bsqueda en un rbol de ese tipo siempre requiere menos de Jog 2 n visitas a nodos
nos al ptimo se llama el mtodo exhaustivo. En lugar de constrmr el ar bol del tope aunque cada visita requiere dos comparaciones por separado. '
hacia abajo, como en el mtodo de balanceo, el mtodo exhaustivo ':onstruye el r-
bol de abajo hacia arriba. El mtodo usa una lista lineal doblemente hgada en la cual Arboles balanceados
cada elemento contiene cuatro apuntadores, un valor de llave y tres valores de ~ro-
babilidad ..Los cuatro apuntadores son apuntadores izquierdo y derecho a la hsta Como observamos con anterioridad, si la probabilidad de buscar una clave en
usados para organizar Ja lista doblemente ligada _Y apuntado~es a los s.ub:boles ' upatabla es la misma para todas las llaves, la bsqueda ms eficiente se efeeta en
izquierdo y derecho para tomar en cuenta lo.s subarboles de busqueda bmana que un ~bol binario balanceado. Desafortunadamente, el algoritmo de bsqueda e in-
contienen llaves menores que, y mayores que el valor de llave en el nod~. L~s tres sercion presentado prevrnmente no asegura que el rbol permanezca balanceado; el
valores.de probabilidad son la suma .de las probabilidades en el subrbol izqmerdo,
grado ~e balanceo dep~nde del orden en que son insertadas las llaves en el rbol. Nos
llamada la probabilidad izquierda, ]a.probabilidad p(i) del valor de la llave del nodo gustana tener un algontmo de bsqueda e insercin eficiente que mantenga el rbol
k(i), llamada la probabilidad de llave y la suma de las prqbabilidades en el subr?ol de bsqueda como un rbol binario balanceado.
derecho llamada probabilidad derecha. La probabilidad total de un n~d? ~e defme Definamos primero de manera ms precisa la nocin de un rbol "ba-
como la suma de sus probabilidades izquierda, derecha y de llave . Al imc1.o.' hay. n
lanceado". La altura de un rbol binario es el nivel mximo de sus hojas (tambin se
nodos en la lista. El valor de la llave en el nodo i-simo es k(i), su probab1hdad iz-
conoce a veces como la profundidad del rbol). Por conveniencia, la altura del rbol
<lUierda es q(i-:- 1) su probabilida.d derecha es q(i), su. probabilidad de llave esp(i),
nulo se defme c?mo -1. Un rbol binario balanceado (a veces.llamado rbol AVL)
y. sus apuntadores a los subrbole.s izquierdo y derecho son nulos. . . es un rbol binario en el cual las alturas de los dos subrboles de todo nodo difieren
Cada iteracin del algoritmo encuentra el primer nodo nd en.la hsta cuya pro-
a lo sumo en 1. El balance de un nodo en un rbol binario se define como la altura de
babilidad total es menor o igual que la de. su sucesor (si no hay nodo que lo cumpla,
~u subrbo,l izquierdo menos la altura de su subrbol derecho. La figura 7.2.4a
nd se hace igual al ltimo nodo de la lista). La llave en nd se convierte en. la raz de
ilustra un arbol bmario balanceado. Cada nodo en un rbol binario balanceado
un subrbol de bsqueda binaria cuyos subrboles derecho. e izquierdo son los su-
tiene balance igual a l, -1 o O, dependiendo de si la altura de su subrbol izquierdo
brboles derecho e izquierdo de nd. Nd se elimina entonces de.la lista. El apuntador
es mayor que, menor que o igual a la altura de su subrbol derecho. En la figura
al subrbol izquierdo de .su sucesor (si lo hay) y el apuntador al subrbol. ?erec~o
7.2.4a se muestra el balance de cada nodo.
de su antecesr (s lo hay) se hacen apuntar al nuevo subrbol, la prob~b1hdad iz,
. Supn.gase q~e tenemos. un rbol binario balanceado y usamos el algoritmo de
quier da de su sucesor y la. probabilidad derecha de .su antecesor se hacen iguales a la
?usqueda e mserc1on precedente para insertar un nodo p en dicho rbol. Entonces el
probabilidad total de nd. Este proceso se repite hasta que slo quede un .nodo en
ar.bol resultante puede o no permanecer balanceado. La figura 7.2.4b ilustra todas
la lista. (Obsrvese que no es necesario comenzar con un completo recorndo. de la
las p~sibles inserciones que pueden hacerse en el rbol de la figura 7 .2.4a. Cada
lista desde el principio de la misma en. cada iteracin; sl.o es necesario empezar a
1~sercion que produce un rbol binario balanceado se indica con una B. Las inser-
partir del segundo antecesor del nodo eliminado en la iteracin previ~.) Cuand? s(o
c10.nes desb.alanceadas se indican con una U y estn enumeradas del J al 12. Es fcil
queda un nodo en la lista, su llave se coloca en la raz del rbol de busqued bmana
ver que el arbol se vuelve desbalanceado si y slo si el nodo recin insertado es un
final con los apuntadores derecho e izquierdo del nodo como apuntadores a los
' . . descendiente izquierdo de un nodo que tena de manera previa un balance de (esto
subrboles derecho e izquierdo de la raz. . . o~urre en los casos U! hasta U8 de la figura 7.2.4b) o si es un hijo derecho descen-
Otra tcnica para reducir el tiempo promedio de bsqueda, cuando se conocen
diente de un. nodo que tena de manera previa balance ~1 (casos del U9 al Ul2).
l.as probabilidades, es un rbol de divisin. Un ,rbol as contiene. ~os ll~ve? en lugar
de una en e.acta nodo. La primera, llamada llave de nodo, se verifica s1 es igual a la . . . En la figura 7.2.4b, el ancestro ms joven que se vuelve des balanceado en cada
llave argumento. Si son iguales, la bsqueda culmina con xito; si lJO, l~ ~l~;e argu- msercin se. indica mediante los nmeros contenidos en tres de los nodos.
mento se compara con la segunda llave en el nodo, Uamad_a la llave de d,vston, par~ , Exammemos algo ms el subrbol con raz eh el ancestro ms joven que se vol-
determinar si la bsqueda debe continuar en el subrbol izquierdo o derecho. Un ti- vera des balanceado como resultado de una insercin. Ilustramos el caso donde el
po particular de rbol de divisin, llamado. rbol de divisi~ por medi~nas, coloca balance de este subrbol fe de manera previa 1, dejando el otro caso como ejercicio
como llave de nodo a la ms frecuente de las llaves en el subarbol con raiz en ese no- al lector. La figura 7.2.5 Ilustra este caso. Denominemos al nodo des balanceado A.
do y coloca la llave de divisin igual a la mediana de las llaves en ese subrbol (es
Bsqueda 419
Estructuras de datos en C
418
o

(a)

1 1 1 \.
1 1 1 \
I \ 1 1
1 1 I \
I \ 8 8
UI U2 U3 U4

1 1 / 1 I \ I \
1 \ I \ 1 1 I \
I \ I \ 1 1 I \
US ['6 U7 U8 U9 UIO UII Ul2
(b)

Figura 7 .2.4 Un rbol binario balanceado y posibles adiciones:

Nodo 1

Como A tena un balance de 1, su subrbol izquierdo no era nulo; podemos en con, recin
secuencia designar a su hijo izquierdo como B. Dado que A es el ancestro ms joven insertado
del nuevo nodo que se volver desbalanceado, el nodo B debe haber tenido un ba, Figura 7.2.5 Insercin inicial;
lance de O. (Se pide al lector probar la afirmacin anterior como ejercicio). As, el (b) todos los balanceos son anteriores
nodo B debe haber tenido (antes de la insercin) subrboles izquierdo y derecho a la insercin.

420 Estructuras de datos en. C


Bsqueda 421
de igual altura n donde es posible que n = -1.) Dado que el balance de A fue l, el
subrbol derecho de A debe tambin haber sido de altura n.
Hay ahora dos casos por considerar, ilustr.ados en la figura 7.2.5a y b. En la
D
figura 7.2.5a el nodo recin creado se inserta en el subrbol izquierdo de B, cam-
biando el balance de B a 1 y el de A a 2. En la figura 7.2.5b el nodo recin creado se
inserta en el subrbol derecho de B, cambiando el balance de B a -1 y el de A a 2.
Para que el rbol se mantenga balanceado es necesario realizar una transformacin
en el mismo de manera que

l. el recorrido en orden del rbol transformado sea el mismo que para el rbol
original (es decir, que el rbol transformado siga siendo un rbol de bsqueda
binaria)
2. el rbol transformado est balanceado.
{a) Arbol original. (b) Rotacin derecha.
Considerar los rboles de las figuras 7 .2.6a y b. El rbol de la figura 7 .2.6b se
dice que es una rotacin derecha del rbol con raz en A de la figura 7.2.6a. De.ma-
nera similar, el rbol de la figura 7.2.6c se dice que es una rotacin izquierda del r-
bol con raz en A de la figura 7.2.6a.
Un algoritmo para implantar una rotacin izquierda de un subrbol con raz r;
en p es el siguiente:

q = right(p);
hold = left ( q);
left(q) = p;
right(p) = hold;

Llamemos a esta operacin leftrotation(p). rightrotation(p) puede definirse de


manera similar. Por supuesto, en cualquier rotacin debe cambiarse el valor del
apuntador a la raz del subrbol que est siendo rotado para que apunte a la nueva
raz. (En el caso de la rotacin izquierda anterior, esta nueva raz es q). Obsrvese (e) Rotacin izquierda.
que el orden de los nodos en un ..recorrido en orden se preserva en ambas rotaciones:
izquierda y derecha. Por consiguiente, se deduce que cualquier nmero de rota- Figura 7.2.6 Rotacin simple en un rbol.
ciones (izquierdas o derechas) pueden ejecutarse en un rbol desbalanceado para
obtener uno balanceado, sin perturbar el orden de los nodos en un recorrido en
orden. puede ser el nodo recin insertado en euyo caso n = -1, o el nodo recin insertado
Regresemos ahora a los rboles de la figura 7.2.5. Supngase que se realiza puede estar en el subrbol derecho o izquierdo de C. La figura 7 .2.5b ilustra el easo
una rotacin derecha en el subrbol con raz en a de la figura 7.2.5a. El rbol re- en que est en el subrbol. izquierdo; el anlisis de los otros casos es anlogo.)
sultante se muestra en la figura 7.2.7a. Obsrvese que el rbol de la figura 7.2.7a Supngase que una rotacin izquierda del subrbol con rz en B precede a una rota-
produce el mismo recorrido en orden que el de la figura 7.2.5a y tambin est balan- cin derecha del subrbol en raz en.A. La figura 7.2.7b ilustra el rbol resultante.
ceado. Tambin, como la altura del subrbol de la figura 7.2.5a eran + 2 a.ntes de la Verificar que los recorridos en orden de los dos rboles son iguales y que el rbol de
insercin y la altura del subrbol de la figura 7.2.7a es n + 2 con el nodo insertado, la figura 7 .2.7b est balanceado. La altura del rbol de la figura 7.2.7b es n + 2, que
el balanee de cada ancestro del nodo A no se altera. As, remplazando el subrbol de es la misma altura del rbol de la figra 7.2.5b antes de la insercin, de manera que
la figura 7 .2.5a por su rotacin derecha de la figura 7.2. 7a garantizamos que se man- el balance en todos los ancestros de A no cambia. Por lo tanto, seguimos teniendo
tenga como rbol de bsqueda binaria balanceado. un rbol de bsqueda balanceado remplazando el rbol de la figura 7.2.5b por el de
Volvamos ahora al rbol de la figura 7.2.5b, donde el nodo recin creado se in- la figura 7.2.7b, siempre que esto ocurra despus de la insercin.
serta en el subrbol derecho de B. Sea C el hijo derecho de B. (Hay tres casos: C

Bsqueda 423
422 Estructuras de datos en C:
/* PARTE l.: Bsqueda e insercin en el rbol binario *I
fp = null;
p = tree;
fya = null;
ya= p;
I* ya apunta al ancestro ms joven que puede *I
/* llegar a desbalancearse. fya seala al padre *I
I* de ya, y fp al padre de p *I
while (p != null) {
i f (key == k(p))
return(p);
q (key < k(p)) ? left(p) right(p);
i f (q !=. null)
i f (bal(q) != O)
(a) fya = p;
ya = q;
I* findeif 1
fp p;
p = q;
llindewhileI
I * insertar nuevo registro *I
q = maketree(rec, key)
bal(q) = O;
(key < k(fp)) ? left(fp) = q : right(fp) = q;
I* el balance de todos los nodos entre node(ya) y node(q) *I
I* deber alterarse, modificando su valor de O *I
p = (key < k(ya)) ? left(ya) : right(ya);
s = p;
while (p != q) {
i f (key < k(p))
bal(p) = 1; /

p left(p);
Figura 7.2.7 Tras rebalancear,
todos los balanceos son despus else
(b) de la insercin. bal(p) = -1;
p = right(p);
/* fin de if */
Presentemos ahora un algoritmo para buscar e insertar en un rbol binario /* findewhle *I
balanceado que no est vaco. Cada nodo del rbol contiene cinco campos: k y r, que I * PARTE II : Determinar si el.rbol se encuentra *I
I* desbalanceado o no. Si lo est, q es el nodo *I
guardan la llave y el registro de manera respectiva, left y right que son apuntadores a
I* reci~ insertado, ya" es su ancestro desbalanceado ms joven, *I
los subrboles izquierdo y derecho de manera respectiva y bal, cuyo valor es 1, -1 o
I* {ya es el padre de ya y s es el hijo de ya en la *I
O dependiendo del balance del nodo. En la primera parte del algoritmo, si la llave
I* direccin del desbalance *I
deseada an no se encuentra en el rbol, se inserta un. nuevo nodo en el rbol de bs- imbal = (key.< k(ya)) ? 1 : -1;
queda binario, sin importar el balance, La primera fase tambin toma en cuenta al i f (bal(ya) D) {
ancestro ms joven, ya que puede desbalancearse tras la insercin. El algoritmo hace I* Se le ha agregado otro nivel al rbol *I
uso de la funcin maketree descrita con anterioridad y de las rutinas rightrotation y I* El rbol permanece balanceado *I
/eftrotation, que aceptan un apuntador a la raz de un subrbol y ejecutan la rota- bal(ya) ibal;
cin deseada. return(q);
I* findeif *I

424 Estructuras de datos en C


425
i f (bal(ya) != imbal) { else {
I* el nodo agregado se ha colocado en la */ I* ver las figuras 7.2.Sb y 7.2.7b *I
I* direccin opuesta del desbalance. */ /* slo que el nuevo nodo se insert en t3 *I
/* El rbol permanece balanceado */ bal(ya) = O;
bal(ya) O; bal(s) = imbal;
return ( q); /* fin de if */
I* fin de if */ bal(p) = O;
I* PARTE III: El nodo adicional a desbalanceado al rbol. I* fin de if */
I* Restablecer el balance efectuando la rotacin requerida, I* ajustar el apuntador del subrbol rotado *I
I* ajustando despus los valores de balance de los nodos involucrados i f ( fya == null)
i f (bal(s) == imbal) { tree Pi
!* ya y s se han desbalanceado en la misma direccin else
!* observar figura 7.2.5a donde ya '? a y s = b ) (ya== right(fya)) ? right(fya) p left(fya) = p;
p = s; return(q);
i f (imbal == 1)
rightrotation(ya);
La altura mxima de un rbol de bsqueda binaria balanceado es 1.44 log 2 n,
else
de manera que una bsqeda en un rbol as nunca requiererns de 44 por ciento de
leftrotation(ya);
bal(ya) = O;
comparaciones que las necesarias en un rbol balanceado d manera completa. En la
bal(s) = O; prctica, los rboles de bsqueda binaria balanceados se comportan an mejor, pro-
} duciendo tiempos de busqueda del orden de log 2n + 0.25 para valores grandes de
else { n. Er promedio, se requiere una rotacin en el 46.5 por ciento de las inserciones
!* ya y s se encuentran desbalanceados en direcciones *! El algoritmo para eliminar un nodo de un rbol de bsqueda binaria balancea-
I* opuestas; ver figura 7.2.Sb *! do conservando su balance, es an ms complejo. Mientras que la insercin requiere
i f (imbal == 1) { a lo sumo una rotacin doble, la eliminacin puede requerir una rotacin (doble o
p = right(s); simple) en cada nivel del rbol, o O(log n) rotaciones. Sin embargo, en la prctica,
leftrotation ( s) se ha visto que slo s requiere un promedio de 0.214 rotaciones (simples o dobles)
left(ya) = p;
rightrotation(ya);
por eliminacin.
Los rboles de bsqueda binaria balanceados que hemos visto se llaman rbo-
}
else { les de altura balanceada porque su altura se usa como criterio para el balanceo. Hay
p=left(s); un gran nmero de formas diferentes para definir rboles balanceados. En un mto-
right(ya) = p; do, se define el peso del rbol como el nmero de nodos externos en el mismo (que es
rightrotation(s); igual al nmero de apuntadores nulos). Si el cedente del peso del subrbol izquierdo
leftrotation(ya); de todo nodo entre el peso del subrbol con raz en el nodo est .entre alguna frac-
/* fin de if */ cin a y 1 - a, el rbol es un rbol de pesos balanceados de razn a o se dice que
! * ajustar el campo bal para los nodos involucrados */ est en la clase wb[a]. Cuando una insercin o eliminacin ordinaria en un rbol
i f (bal(p) == O) ( de clase wb[a] elimina al rbol de dicha clase, se usan rotaciones para restaurar la
I* p fue un nodo insertado */ propiedad de pesos balanceados.
bal(ya) = O;
Otro po de rbol, llamado por Tarjan, un rbol binario balanceado, requiere
bal(s) = O;
que para todo nodo nd, la longitud del camino ms largo de nd a un nodo externo
else sea a lo sumo dos veces la longitud del camino ms corto de nd a un nodo externo.
i f (bal(p) == imbal) { (Recurdese que los nodos externos son nodos agregados al rbol en cada apuntador
!* ver las figuras 7.2.Sb y 7.2.7b * /' nulo.) De nuevo, se usan rotaciones para mantener el balance despus de. una inser-
bal(ya) = -imbal; cin o eliminacin. Los rboles balanceados de Tarjan tienen la propiedad de que,
bal(s) = O; tras una eliminacin o insercin, puede restaurarse el balance aplicando a lo sumo
una rotacin doble funa simple, en contraste con las posibles O(log n) roiaciones
tras la eliminacin en un rbol de altura balanceada.

426 Estructuras de datos en C Bsqueda 427


7.2.9. Considere los rboles de bsqueda de la figura 7 .2.8.
Los rboles balanceados tambin pueden usarse para una implantacin efi. a. Cuntas permutaciones de los enteros del 1 al 7 produciran los rboles de bs-
ciente de colas de prioridad (ver secciones 4.1 y 6.3.) La insercin de un nnevo ele- queda binaria de la figura 7 .2.8a, b y c, de manera respectiva?
b. Cuntas permutaciones de los enteros del 1 al 7 producen rboles de bsqueda
mento requiere a lo sumo O(lof n) pasos para encontrar la posicin adecuada y 0(1)
binaria similares a los de la figura 7 .2.8a, by c, de manera respectiva? (Ver el ejer-
pasos para accesar el elemento (siguiendo los apuntadores izquierdos hasta la hoja
cicio 5.1.9.)
de la extrema izquierda) y O(log n) 0(1) pasos para eliminar esa hoja. As, al igual c. Cuntas permutaciones de los enteros del 1 al 7 producen rboles de bsqueda
que una cola de prioridad implantada usando un heap (seccin 6.3), una cola de binaria con el mismo nmero de nodos en cada nivel como los rboles de la figura
prioridad implantada usando un rbol balanceado puede ejecutar cualquier secuen- 7 .2.8a, b y e de manera respectiva?
cia de n inserciones y eliminaciones mnimas en O(n log n) pasos. d. Encuentre una asignacin de probabilidades a los siete primeros nmeros enteros
positivos como argumentos de bsqueda que hagan a cada uno de los rboles de la
figura 7.2.8a, b y e ptimos.
EJERCICIOS 7.2.10. Muestre que el rbol de Fibonacci de orden h + 1 (ver ejercicio 5.3.5) es un rbol
balanceado por altura con altura h y tiene menos nodos que cualquier otro rbol
7.2.1. Escriba un algoritmo de insercin eficiente para un rbol de bsqueda biriaria con el balanceado por altura con altura h.
fin de insertar un nuevo registro cuya llave se sabe que no est en el rbol.
7,2.2. Muestre que es posible obtener n rbol de bsqueda binaria en eLcual existe slo una
hoJa, aun cuando los elementos del rbc,I no se inserten obligatoriamente en orden as.:._, ARBOLES DE BUSQUEDA GENERALES.
cendente o desc~ndente,
7.2.3. Verifique mediante simulacin que_ si se presentan registros en orden aleatorio al algo-, Los rboles no binarios generales tambin se usan como tablas de bsqueda, en par-
ritmo de bsqueda e insercin para un rbol bip.ario, el nmero de comparaciones d_e'. ticular en la memoria externa. Existen dos grandes categoras de dichos rboles: r-
llave es O(log n). boles de bsqueda de .accesos mltiples y rboles de bsqueda digitales. Examinaremos
7.2.4. Demuestre que no todo rbol de bsqueda binaria den nodos tie_ne igual probabili- cada uno de ellos.
dad (suponfendo que los n6d6s se inserta_n en orden aleatorio), y que los rboles ba-
lance_ados son ms. pr<;1bables que los rboles 'rectilneos'. Arboles de bsqueda de accesos mltiples
7.2.5. Escriba un algoritmo pra eliminar un nodo de un rbol binario que remplace el nodo.
p>r su antecesor en orden,. en lugar de por 'su sucesor: _en orden~ . En un rbol de bsqueda binaria, cada nodo nd contiene una sola llave y apun-
7.2.6. Suponga que el tipo de nodo de un rbol de bsqueda binaria se define de la siguiente ta a dos subrboles. Uno de esos subrboles contiene todas las llaves del rbol con
manera: raz en nd que son menores que la llave en nd y el otro subrbol contiene todas las
llaves en el rbol con raz en nd que son mayores que (o iguales a) la llave en nd.
struct _llode_type Podemos extender este concepto a un rbol de bsqueda general en el cual
int k; cada nodo contiene una o ms llaves. Un rbol de bsqueda de accesos mltiples de
int r;
orden n es un rbol general en el cual cada nodo tiene n o menos subrboles y con-
struct nodetype *left;
tiene una llave menos que la cantidad de subrboles. Es decir, si un nodo tiene
struct nodetjpe *right;
cuatro subrboles, contiene tres llaves. Adems, si s 0 , Si, ... , Sm-i son los m subr-
boles de un nodo que contiene llaves k 0 , k, ... , km_ 2 en orden ascendente, todas las
llaves en el subrbol s 0 son menores o iguales que k 0 , todas las llaves en el subrbol sj
Los campos r y k contienen el registro y la llave del nodo; /eft y right son apuntadores (donde j est entre 1 y m - 2) son mayores que kj _ 1 y menores o iguales que kj, y
a_los h_ijos del nod(), Escribir u_na rutina en C sinsrt(tree, key, rc} Para buscar e in- todas las llaves en el subrbol sm _ 1 son mayores que km _2 El subrbol sj se llama el
sertar un registro rec_ con llave key ei1 un rb._ol de bsqueda binaria apuntado por
tree. subrbol izquierdo de llave k; y su raz el hijo izquierdo de llave kj. De manera simi-
lar s-se llama el subrbol derecho y su raz el hijo derecho, de llave kj-l Uno o ms
7.2.7. Escrib uha rutina en C, sde/ete(tiee, key) para buscar_y eliminar un registro record
con llave key de un rbol de bsqueda binari i,ml)lntado coIIlo en el ejercicio subrboles de un nodo pueden estar vacos. (Algunas veces, se usa el trmino "rbol
previo. Si s encuentra dichO registro, la funcin regresa el valor de su c"ampo'r; si no, de bsqueda de accesos mltiples" para hacer referencia a. cualquier rbol no bina-
regresa O. rio usado para la bsqueda, incluyendo los rboles digitales que presentaremos al
7.2.8. Escriba una rtina en C delete''(tree, keyl, ke.Y2) para eliminar todos los fegistros con final de esta seccin. Sin embargo, nosotros usamos el trmino slo para rboles que
llaves (inclusive) entre keyl y key2 de un rbol de bsqueda binaria cuyos nodos estn puedan contener llaves completas en cada nodo.)
declarados como en los ejercicios previos.

428 Estructuras de datos en e Bsqueda 429


La figura 7.3.1 ilustra un rbol de bsqueda multivas. La 7.l.3a muestra uno
de orden 4. Los ocho nodos de ese rbol se etiquetaron desde A hasta H. Los nodos
4 A, D, E y G contienen el nmero mximo de subrboles, 4, y el nmero mximo de
naves, 3. Nodos de ese tipo se llaman nodos llenos. Sin embargo, algunos de los su-
brboles de los nodos D y E y todos los subrboles de nodos G estn vacos como se
2 indica mediante flechas que parten de las posiciones correspondientes en los uodos.
, Los nodos B, C, F y H no estn llenos y tambin contienen algunos subrboles
vacos. El primer subrbol de A contiene las llaves 6 y 10, ambas menores que 12,
que es la primera llave de A. El segundo subrbol de A contiene 25 y 37, ambas ma-
yores que 12 (que es la primera llave de A) y menor que 50 (la segunda llave de A).
(a)
La tercera llave contiene 60, 70, 80, 62, 65 y 69, de los cuales todos estn entre 50 (la
,egunda llave de A) y 85 (la tercera llave). Finalmente, el ltimo subrbol de A con-
tiene 100, 120, 150 y 110, todas mayores que 85 la ltima llave en el nodo A. De
manera similar, cada subrbol de cualquier otro nodo contiene slo llaves entre las
dos llaves de ese nodo y sus ancestros.
La figura 7 .3.1 b ilustra un rbol de bsqueda multivas de arriba abajo. Tal r-
bol se caracteriza por la condicin de que todo nodo lleno es una hoja. Advirtase
que el rbol de la figura 7.3.la no es "de arriba abajo", dado que el nodo C lleno
contiene un subrbol no vaco. Definir una semihoja como un nodo con un subrbol
vaco l menos. En la figura 7. 3.1 a, los nodos del B al H son todos semihojas. En la
figura 7 .3.1 b, los nodos del B al G y del / al R son semihojas. En un rbol de
multivas de arriba abajo, una semihoja tiene que estar llena o bien ser una hoja.
La figura 7.3. lc es otro rbol de bsqueda de accesos mltiples de orden 3. No
es de "arriba abajo" dado que hay cuatro nodos con slo una llave y subrboles que
(b) no estn por completo vacos. Sin embargo, tiene la propiedad especial de ser balan-
ceado. Es decir, todas sus sernihojas estn en el mismo nivel (3). Esto implica que
todas las semihojas son hojas. Ni el rbol de la figura 7.3.la (que tiene hojas en los
7 niveles 1 y 2) ni el de la figura 7 .3.1 b (con hojas en los niveles 2, 3 y 4) son rboles de
bsqueda de accesos mltiples balanceados. (Ntese que aunque un rbol de bs-
queda binaria es un rbol de bsqueda de accesos mltiples de orden 2, un rbol de
bsqueda binaria balanceado como se defini al final de la seccin 7 .2 no es por
fuerza balanceado como rbol de bsqueda de accesos mltiples, dado que puede
tener hojas en diferentes niveles.)

Bsqueda en un rbol de accesos mltiples

El algoritmo para buscar en uo rbol de bsqueda multivas, sin tener en cuen-


ta si es "de arriba a abajo" o no, balanceado o no, es directo. Cada nodo contiene
un solo campo entero, un nmero variable de campos apuntadores y un nmero va-
riable de campos llaves. Si node(p) es un nodo, el campo entero numtrees(p) es igual
al nmero de subrboles de node(p). numtrees(p) siempre es menor o igual que el
orden del rbol, n. Los campos apuntadores son(p, O) hasta son(p, numtrees(p)-1)
apuntan a los subrboles de node(p). Los campos llaves k(p, O) hasta k(p,
numtrees(p)-2) son las llaves contenidas en node(p) en orden ascendente. El su-
k) Figura 7.2.8 brbol al que apunta son(p, i) (para i entre 1 y numtrees(p)-2 inclusive) contiene
todas las llaves del rbol entre k(p, i-1) y k(p, i). son(p, O) apunta a un subrbol

430 Estructuras de datos en C


Estructuras de datos en C 431
que contiene slo llaves menores que k(p, O) y son(p, numtrees(p)-1) apunta a un
subrbol que contiene slo llaves mayores que k(p, numtrees(p)-2).
Tambin suponemos una funcin nodesearch(p, key) que da como resultado el
menor entero}, tal que key < = k(p,j), o numtrees(p)-1 si key es mayor que todas
las llaves en node(p). (Discutiremos en breve cmo se implanta nodesearch). El si-
100 l~O 150
guiente algoritmo recursivo es para una funcin search(tree) que da como resultado
un apuntador al nodo que contiene key ( -1 [representando nulo O] si no hay tal
nodo en el rbol) y hace la variable global position igual a la posicin de key en ese
nodo.
H

110
p = tree;
i f (p == null)
fa)
position = -1;
return(-1);
I* findeif */
i = nodesearch(p, key);
i f (i < numtrees(p) - 1 && key k(p,i)) {
position = i;
return(p);
/*. fin de if */
return(search(son(p,i)));
/,
Obsrvese que despus de hacer i igual a nodesearch(p, key), insistimos en ve-
160
rificar que i < numtrees(p)-1 antes de accesar k(p, i). Esto es para evitar el uso de
k(p, numtrees(p )-1) errneo o no existente, en el caso que key sea mayor que todas
\" . (} p las llaves en node(p). Lo que sigue es una versin no recursiva del algoritmo ante-
111 1~s 1.:io
rior.

p = tree;
~hile (p != null) {
I * Buscar el subrbol rotado en node(p) */
i = nodesearch(p, key);
'" i f (i < numtrees(p) - 1 && key == k(p,i))
position = .:t.;
return(p);
I* fin de if */
p = son(p,i)
f* fin de while *I
position = -1;
return(-1);

La funcin nodesearch es responsable de la localizacin de a llave ms pe-


30 hO '70 1~o 1 ~o quea en un nodo mayor o igual que el argumento de bsqueda. La tcnica ms
simple para hacer esto es una bsqueda secuencial a travs del conjunto ordenado de
llaves en el nodo. Si todas las llaves son de igual longitud fija, se puede usar tambin
una bsqueda binaria para localizar la llave apropiada. La decisin de cundo usar
" 80 1.10 lhO
bsqueda secuencial o binaria depende del orden del rbol que determina cuntas

(e)

432 Figura 7.3.1 Arboles de bsqueda multivas. Bsqueda 433


el rbol del orden-!! slo requiere 400 nodos (de 10 llaves cada uno). La profundi-
llaves tienen que ser buscadas. Otra posibilidad es organizar las llaves dentro del no-
dad del rbol de orden-5 es por lo menos 5 (nivel Ocon un nodo, nivel I con 5, nivel 2
do como un rbol de bsqueda binaria.
con 25, nivel 3 con 125, nivel 4 con 625 y nivel 5 con las 219 restantes), mientras que
la profundidad del rbol de orden-!! puede ser tan pequea como 3 (nivel O con un
Implantacin de un rbol de accesos mltiples
nodo, nivel l con 11, nivel 2 con 121, nivel 3 con las 267 restantes). As, para buscar
en el rbol de orden-5 tienen que accesarse 5 6 nodos para la mayora de las llaves y
Ntese que hemos implantado un rbol de bsqueda multivas de orden n
en el rbol de orden-11 slo tienen que accesarse 3 4 nodos. Pero como observa-
usando nodos de hasta n hijos en lugar de como un rbol binario con apuntadores a
mos arriba, el acceso de un nodo es la operacin ms costosa en la bsqueda de la
hijo y hermano, como se esboz en la seccin 5.5 para rboles generales. La razn
memoria externa, donde son. usados con ms frecuencia los rboles multivas. As,
para esto es que en rboles de accesos mltiples, a diferencia de rboles en general
un rbol de orden. mayor conduce a un proceso de bsqueda ms eficiente. La me-
hay un lmite para el nmero de hijos de un nodo y podemos esperar que la mayor~
moria real requerida por ambas situaciones es aproximadamente la misma, dado
de los nodos estn tan llenos como sea posible. En un rbol general no haba tal
que, aunque se requieren menos nodos grandes para guardar un archivo de un tama-
lmite, y muchos nodos podan contener slo uno o dos elementos. En consecuencia
o dado, cuando el orden es grande, cada nodo es ms grande.
la flexibilidad de permitir tantos o tan pocos elementos como fuera necesario y el
Como el tamao de un nodo est en general fijado por otros factores externos
ahorro de espacio cuando un nodo estaba casi vaco hacan meritoria la sobrecarga
(por ejemplo, la cantidad de memoria que se lee fsicamente de un disco en una ope-
de los apuntadores a hermanos adicionales.
racin), un rbol de orden mayor se obtiene guardando los registros fuera de los
No obstante, cuando los nodos. no estn llenos, la implantacin de rboles
nodos del rbol. Aun cuando esto causa una lectura externa. extra para obtener un
multivas que hicimos aqu usa una cantidad considerable de memoria. A pesar de
registro despus de que su llave ha sido localizada, guardat'los registros dentro de un
este posible gasto de memoria, los rboles multivas se usan con bastante frecuencia
nodo reduce de manera tpica el orden en un factor entre 5 y 40 (que es el rango
para almacenar datos, en especial en un dispositivo externo de acceso directo como
tpico del cociente del tamao del registro al de la llave), de manera que el cambio no
un disco. La razn para ello es que el acceso de cada nodo nuevo, durante una bs-
vale la pena.
queda, requiere de la lectura de un bloque d memoria del dispositivo externo. Esta
Si se guarda un rbol de bsqueda multivas en memoria externa, in\ apunta-
operacin de lectura es relativamente costosa en trminos de tiempo a causa del tra-
dor a un nodo es una direccin de memoria externa que especifica la posicin de co-
bajo mecnico involucrado en colocar el dispositivo de manera apropiada. Sin em-
mienzo de un bloque de almacenamiento. El bloque que compone un nodo tiene cue
bargo, una vez colocado el dispositivo, la tarea de leer una gran cantidad de datos
ser, ledo en la memoria interna antes de que puedan ser accesados cualquiera de los
secuenciales es muy rpida. Esto significa que el tiempo total para leer un bloque de
campos numtrees, k o son. Suponer que la rutina directread(p, block) lee un nodo
memoria (es decir un "nodo") es afectado slo de manera mnima por su tamao.
en la direccin de la memoria externa p dentro de un registro de memoria interna
Una vez ledo y trasladado un nodo a la memoria interna de la computadora, el cos-
block, suponer tambin que los campos numtrees, k y son en el registro se accesan
to de su bsqueda a velocidad electrnica interna es minsculo comparado con el
mediante la notacin similar en C block.numtrees, block.k y block.son. Suponer
costo de su lectura inicial en la memoria. Adems, el almacenamiento externo es
adems que se modifica la funcin nodesearch para aceptar un bloque (interno) de
poco costoso de manera que una tcnica que perfeccione la eficiencia en tiempo a
memoria en lugar de un apuntador (es decir, se invoca mediante nodesearch(block,
expensas del espacio de almacenamiento externo es efectiva en costo real (es decir,
key) Y no mediante nodesearch(p, key)). Entonces el siguiente es unalgoritmo no re-
en dinero). Por esta razn, los sistemas de memoria externa basados en rboles de
cursivo para buscar en un rbol de bsqueda multivas almacenado en la memoria
bsqueda multivas intenta incrementar al mximo el tamao de cada nodo, y rbo-
externa.
les de orden 200 ms no son poco comunes.
El segundo factor a considerar en la implantacin de rb9les de bsqueda
multivas es el almacenamiento de los propios registros de datos. Como en cualquier p = tree;
while (p != null) {
sistema de memoria, los registros pueden ser almacenados con las llaves o lejos de
directread(p, block);
ellas. La primera tcnica requiere guardar registros completos dentro de. los nodos i = nqdesearch(block, key);
del rbol, mientras que la segunda requiere guardar un apuntador al registro aso- i f (i < block. numtrees - 1 && key block. son( i)) {
ciado con cada llave en un nodo. (Una tercera tcnica involucra la duplicacin de las position = i;
llaves y guardar los registros slo en las hojas. Este mecanismo se discute ms tarde return ( p);
con mayor detalle cuando presentamos los B + -rboles.). /* findeif */
En general, quisiramos guardar tantas llaves como sea posible en cada nodo. p = bloCk.son(i);
Para ver por qu esto es as, considerar dos rboles "de arriba a abajo" con 4000 lla- I * fin de whie * /
ves y profundidad mnima, uno de orden 5 y otro de orden 11. El rbol de orden-5 position = -1;
requiere 1000 nodos (de 4 llaves cada uno) para guardar las 4000 llaves, mientras que return(-1);

Bsqueda 435
434 Estructuras de datos en C
El algoritmo tambin hace block igual al nodo en la direccin externa p. El registro accesar a su padre y su campo index y determinar cul llave del nodo padre dar como
asociado a key o un apuntador a l puede encontrarse en block. 'Ntese que nul/, tat salida y cul subrbol del padre recorrer como paso siguiente. Sin embargo, esto
y como se usa en este algoritmo, se refiere a una direccin de memoria externa nula requerira numerosas lecturas para cada nodo y es probable que no valga la pena el
en lugar del apuntador nulo de C. ahorro de espacio de los buffers, especialmente debido a que un rbol de orden ele-
vado con un nmero muy grande de llaves requiere una profundidad muy pequefia.
Recorricjo de un rbol multivias (Como ya se ilustr, un rbol de orden 11 con 4000 llaves se puede acomodar muy
. bien con profundidad 3. Un rbol de orden 100 puede acomodar cerca de nn milln
Una operacin comn con una estructura de datos es el recorrido: acceso de de llaves con una profundidad de slo 2.)
todos los elementos de la estructura en una secuencia fija. Lo que sigue es un algorit- Otra operacin comn relacionada muy de cerca al recorrido es el acceso se-
mo recursivo traverse(tree) para recorrer un rbol multivas e imprimir sus llaves en cuencial directo. Esto se refiere a accesar la llave que sigue a una llave cuya localiza-
orden ascendente cin en el rbol es conocida. Supongamos que localizamos una llave k I mediante
una bsqueda en el rbol y que ella est en la posicin k(nl, il). Por lo general, el
i f (tree != null) { sucesor de k I puede encontrarse ejecutando la siguiente rutina next(n 1, i1). (nullkey
nt = numtrees(tr~e); es un valor especial que indica que la llave apropiada no puede ser encontrada.)
for (1 = O; 1 < nt ~ 1; 1++)
traverse(son(tree, i));
p = son ( n1, i1 + 1) ;
printf( 11 %d 11 , k(tree, i));
q= null; !* qestunnododetrsdep *I
/* fin de.fqr .*t whle (p != null) {
traverse(~on(.tree,' nt));
I* fin de if */
q = p;
p = son ( p, o) ;
I* fin
de while */
En la implantacin de la recursividad, tenemos que guardar una pila de apuntadores if null)
(q !=
a todos los nodos en un camino comenzando con la raz del rbol hasta el nodo que return(k(q, O));
est siendo visitado en el momento. i f (11 < numtrees(n1) - 2)
return(k(n1, 11 + 1));
Si cada nodo es un bloque de memoria externa y tree es la direccin de almace- return(nullkey);
namiento de memoria externa del nodo raz, un nodo tiene que leerse en la memoria
interna antes de que puedan ser accesados sus campos son o k. As el algoritmo se Este algoritmo se basa en el hecho de que el sucesor de k I es la primera llave en
convierte en: el subrbol que sigue a kl en node(nl), o si ese subrbol est vaco [son(nl, il + 1)
es igual a nul/] y si k 1 no es la ltima llave en su nodo (i 1 < numtrees(n 1) - 2), el su-
i f (tree != null) { cesor es la llave siguiente en node(n 1).
directread(tree, block); Sin embargo, si kl es la ltima llave en su nodo y si el subrbol que le sigue est
nt = block. numtrees;
vaco, su sucesor slo puede ser encontrado regresndose en el rbol. Suponiendo
for (i = O; i < nt.- 1; i++)
traverse(block.son(1));
campos father e index en cada nodo como se esboz con anterioridad, se puede
printf( 11 %d 11 , block.k(i)); escribir un algoritmo completo para encontrar el sucesor de la llave en la posicin il,
I* fin de fer */ succesor(n 1, il), del nodo apuntado por n I de la siguiente manera:
traverse(block.son(nt));
I* fin de if */ p = son('n1, i:L + 1);
i f (p != null && 11 < numtrees(n1) - 2)
donde directread es una rutina del sistema que lee un bloque de memoria en una di- I* utilizar el algoritmo anterior */
reccin externa particular (lree) dentro del buffer de memoria interna (block). Esto return(next(n1, i1});
requiere tambin guardar una pila de registros. Si des la profundidad del rbol, se f = father(n1);
tienen que guardar en la memoria d + I registros. i = index(n1);
De manera alternativa, casi todos los buffers pueden eliminarse si cada nodo while ( f != null && i numtrees(f) - 1) {
1 = index( f);
contiene dos campos adicionales: un campo father apuntando a su padre y un cam-
f ~ father( f);
po index indicando cul hijo del padre es el nodo. Entonces cuando el ltimo subr-
I * fin de while * /
bol de un nodo ha sido recorrido, el algoritmo usa el campo father del nodo para

436 Estructuras de datos en C Bsqueda 437

__,J
if(f==null)
return(NULLKEY);
return(k(f, i ) ) ;

Por supuesto, quisiramos evitar el regreso en el rbol siempre que sea posibe;
Como un recorrido que inicie en una llave es bastante comn, el proceso de bssl;
queda inicial se modifica con frecuencia para retener en la memoria interna, todos,
los nodos en el camino de la raz del rbol a la llave localizada. Entonces, si setiene:,C
que regresar en el rbol, el camino a la raz est disponible con facilidad. Como ya se
observ, si esto se hace, no se necesitan los campos jather e index.
Posteriormente en esta seccin examinamos una adaptacin especializada de
un rbol de bsqueda multivas, llamado un B +-rbol, que no requiere una pilapara,.i'
el recorrido secuencial eficiente.

Insercin en un rbol de bsqueda de accesos mltiples

Ahora que hemos examinado cmo buscar y recorrer rboles de bsqueda


multivas, hagmoslo con las tcnicas de insercin para esas estructuras. Examina-
mos dos tcnicas de insercin para rboles de bsqueda multivas. La primera es
anloga a la insercin en un rbol de bsqueda binaria y da como resultado un rbol
de bsqueda multivas de "arriba a abajo". La segunda es una nueva tcnica de
insercin y produce un tipo especial de rboles de bsqueda multivas balanceado.
Es esta segunda tcnica, o una leve variacin de la misma, la que se usa con mayor
frecuencia en sistemas de almacenamiento externo de archivos de accesos directo.
Por conveniencia, suponemos que no se permiten llaves duplicadas en el
rbol, de manera que si se encuentra el argumento key no ocurren inserciones. Supo-
nemos que el rbol no est lleno. El primer paso en ambos procedimientos de inser-
cin es buscar el argumento key. Si se encuentra dicho argumento en el rbol, se
obtiene, como resultad9 u.n apuntador al nodo que contiene la llave y se asigna a la
variable position su posicin dentro del nodo, tal y como en el procedimiento de
bsqueda presentado antes. Sin embargo, si no se encuentra. el argumento key, se da
como resultado un apuntador a la semihoja node(s) que contendra la llave si estuvie:.:;
ra presente, y position se hace igual al ndice de la llave. ms pequea en node(s) qutl
sea mayor que .el argumento key (es decir, la posicin del argumento key si estuviera. ~, .
presente en el rbol). Si todas las llaves en node(s) son menores que el argumento .a_

i.1 li
- ...: H

key, position se hace igual a numtrees(s) -1. Una variablejound se hace igual al ver- j ""
dadero o falso dependiendo de si se encuentra o no la llave en el rbol.
La figura 7.3.2 ilustra el resultado de este procedimiento para un rbol de bs-
queda de accesos mltiples balanceado "top-down" de orden-4 y varios argumentos
de bsqueda. El algoritmo para jind es directo:.

q = null;
p = tree;
while (p != null) 1
i "" nodesearch(p, key};
q "' p;
if (i < numtrees(p) - 1 && key k(p, i)) {

438 Estructuras de datos en C


Bsqueda 439
found = TRUE; N Q

fR m
position = i
return(p); I* la llave se encontr en node(p) *!
I* fin de if */
p = son(p, i);
/* fin de while */
(a)
found = FALSE;
position = i;
return(q); f* p es null. q apunta a una semihoja *! L G

70 75 82 12 18 20
Para implantar este algoritmo en C escribiramos una funcin find con el
siguiente encabezamiento:
NODEPTR find(tree, key, pposition, pfound)
71 22
NODEPTR tree;
KEYTYPE key;
int *pposition, *pfound
(b)
Las referencias a position y found del algoritmo se remplazan por referencias a
*pposition y *pfound en for111a respectiva, en la funcin en C.
Supongamos que ses el apuntador al nodo que da como resultado find. El se- L
gundo paso del procedimiento de insercin se aplica slo si no se encuentra la llave
70 75 82
(recurdese que no se permiten llaves duplicadas) y si node(s) no est lleno (es decir,
si numtrees(s) < n, donde n es el orden del rbol). En la figura 7 .3.2 esto se aplica
slo a los casos d y f. El segundo paso consiste en la insercin de la nueva llave (y
registro) dentro de node(s). Obsrvese que si el rbol es "de arriba a abajo" o
balanceado, una semihoja descubierta por find es siempre una hoja. Sea insrec(p, i,
rec) una rutina para insertar el registro rec en la posicin i de node(p) como es apro- 84 86 87
piado. Entonces el segundo paso del prceso de insercin puede describirse como
sigue:
85
r
nt =-numtrees(s).;
numtrees(s) = nt +_ 1;
for (i = nt - 1; i > position; i--)
k(s, i) = k(s, i - 1);
(e) Figura 7~3.3
k(s, position) = key;
insrec(s, position,. rec);

Llamamos a esta funcin insleaf(s, position, key, rec). La primera tcnica de insercin, que produce.rboles de bsqueda multivas de
La figura 7.3.3a ilustra los nodos localizados por el procedimiento/ind en las "arriba a abajo", imita la accin del algoritmo de insercin en un rbol de bsqueda
figuras 7.3.2d y f con las nuevas llaves insertadas. Obsrvese que no es necesario binaria. Es decir, asigna un nuevo nodo, inserta la llave y el registro dentro del mis-
copiar los apuntadores a hijo asociados con las llaves que estn siendo movidas, da- mo y coloca el nuevo nodo como el hijo apropiado de node(s). Usa la rutina
do que el nodo es una hoja, de manera que todos los apuntadores son nulos. Supo- maketree(key, rec) para asignar un nodo, hace a los n apuntadores en l, iguales a
nemos que fueron inicializados a NULL cuando el nodo fue en el inicio agregado al NULL, sus campos numtrees iguales a 2 y su primer campo de llave igual a key. Ma-
rbol. ketree llama despus a insrec para. insertar el registro como es apropiado y da como
Si el paso 2 es apropiado (es decir, si un nodo hoja no lleno ha sido encontra- resultado final un apuntador al nodo recin asignado. Usanclo maketree, la rutina
do) donde puede insertarse la llave, ambas rutinas de insercin terminan. Las dos insfu/1 para insertar la llave cuando la semihoja apropiada est llena puede implan-
tc~icas difieren slo en el tercer paso, que se realiza cuando el procedimiento find tarse de manera trivial como
localiza una semihoja llena.

440 Estructuras de datos en C Bsqueda 441


p = maketree(key, rec); if (numtrees(s) < n) {
son(s, position) = p; insleaf(s, position, key, rec);
return ( s);
/* fin de if */
Si se guardan en cada nodo campos father e index, las operaciones
p = maketree(key, rec);
son(s, position) = p;
father(p) - s; position = O;
index(p) = position; return (p);

se requieren tambin.
La figura 7 .3.3b ilustra el resultado de insertar las llaves 71 y 22, en forma res\,' Arboles-B
pectiva, en los nodos localizados por find en la figura 7.3.2c y e. La figura 7.3.3t
ilustra inserciones subsecuentes de las llaves 86, 77, 87, 84, 85 y 73, en ese orden. N- La segunda tcnica de insercin para rboles de bsqueda multivas es ms
tese que el orden en el cual se insertan las llaves afecta mucho donde stas son colo-. compleja. Sin embargo, esta complejidad se compensa por el hecho de crear rboles
cadas. Por ejemplo, considerar lo que ocurrira si las llaves fueran insertadas en el b'alanceados, de manera que el nmero mximo de nodos accesados para encontrr
orden 85, 77, 86, 87, 73, 84. una llave particular es pequeo. Adems, la tcnica ofrece la ventaja adicional, de.
Obsrvese tambin que esta tcnica de insercin puede transformar una hoja que todos los nodos (exdepto la raz) de un rbol creado mediante ella estn por lo
en no-hoja (aunque permanece como una semihoja) y en consecuencia des balancear menos medio llenos; de manera que se desperdicia' muy poco espacio de memoria,
el rbol multivas. Es por lo tanto posible, que inserciones sucesivas produzcan un Esta ltima ventaja es la razn primaria por la cual se usa con mayor frecuencia la
rbol fuertemente desbalanceado y en el cual un nmero desordenado de nodos sea, segunda tcnica de insercin (o una variacin de ella) en sistemas de archivos reales.
accesado para localizar ciertas llaves. En situaciones prcticas, sin embargo, los r- Un rbol de bsqueda multivas balanceado de orden n en el cual cada nodo-
boles de bsqueda multivas creados mediante esta tcnica de insercin, aunque no n.o raz contiene al menos n/2 llaves se llama un rbol-B de orden n. (Obsrvese que
balanceados en su totalidad, no son muy desbalanceados, de manera que no se acce- la barra en n/2 denota divisin entera de manera que un rbol 1-B de orden 11 con-
san muchos nodos cuando se busca una llave en una hoja. Sin embargo, la tcnica tiene por lo menos 5 llaves en cada nodo-no raz, igual que un rbol-B de orden 10.)
tiene una desventaja fundamental. Como se crean hojas que contienen una sola lla- Un rbol-B de orden n tambin se conoce como rbol n-(n - 1) o rbol (n - 1)-n,
ve, y se pueden crear otras hojas antes de llenar hojas ya existentes, los rboles (La raya fuera del parntesis es un guin mientns que la de adentro es un signo me-
multivas creados por medio de inserciones sucesivas C01! este mtodo gastan mucho nos.) Esto refleja el hecho de que cada nodo en el rbol tiene un mximo.den - I
ms espacio en nodos hoja que estn casi vacos. llaves y n hijos. As, un rbol 4-5 es un rbol-B de orden 5 como lo es un rbol 5-4.
Aunque este mtodo de insercin no,garantiza rboles balanceados, s garanti- Eri particular, un rbol 2-3 (o 3-2) es el rbol-B no trivial (es decir no binario) ms
za rboles "de arriba abajo". Para ver esto, obsrvese que un nodo nuevo no es elemental, como una o dos Haves y dos o tres hijos por nodo.
creado a no ser que su padre est lleno. As cualquier nodo no-lleno no tiene descen- (En este punto, deberamos decir algo acerca de la terminologa. En la discu-
dientes y es;por lo tanto, una hoja, lo que implica :,or definicin que el rbol es "de sin sobre rboles-B, la palabra "orden" la usan de manera diferente autores dife-
arriba abajo". La ventaja de un rbol "de arriba abajo" es que los nodos superiores rentes. Es comn encontrar el orden de un rbol-B definido como el nmero mnimo
estn llenos, de manera que se encuentren tantas llaves como sea posible en pasos de llaves en un nodo no-raz [es decir, n/2] y el grado de un rbol-B como el nmero
cortos. mximo de hijos [es decir, n]. Otros autores usan "orden" para denotar el nmero
Antes de examinar la segunda tcnica de insercin, juntamos todas las piezas mximo de llaves en un nodo [es decir, n - l]. Nosotros usamos orden de manera
de la primera tcnica para formar un algoritmo completo de bsqueda e insercin consistente para todos los rboles de bsqueda multivas para denotar el nmero
para rboles de bsqueda multivas. de :'.arriba abajo". mximo de hijos.)
Los dos primeros pasos de la tcnica de insercin son los mismos para rboles-B
i f (tree -- null) { que para rboles de "arriba abajo". Primero, se usafind para encontrar la hoja en
tree = maketree(key, rc); la cual se debe insertar la llave, y despus si la hoja localizada no est llena, agregar
position = O; la llave usando ins/eaf. Es el tercer paso, cuando la hoja localizada est llena, que di-
return ( tree); fieren los mtodos. En lugar de crear un nuevo nodo con una sola llave, dividir la
I* fin de if */ hoja llena en dos: una hoja izquierda y una hoja derecha. Por simplicidad, suponer
s = find(tree, key, position, found); que n es impar. Las n llaves consistentes en las n - 1 llaves en la hoja llena y la
i f (found -- TRUE) nueva llave a ser insertada, se dividen en tres grupos: las n/2 llaves menores que
return ( s); colocan en la hoja izquierda, las n/2 mayores en la hoja derecha y la del medio [tiene

442 Estructuras de datos 9n G' Bsqueda 443


que haber una dado que 2 * (n/2) es igual a n I sin es impar] se coloca dentro del
nodo padre si es posible (es decir, si el nodo padre no est lleno). Los dos apuntado.
320 540
res a cada lado de la llave insertada dentro del padre se hacen igual a las hojas
, / '--1--......., ........
derecha e izquierda recin creadas, de manera respectiva.
/
/ .... ....
La figura 7.3.4 ilustra este proceso en un rbol-B de orden 5. La figura 7.3.4a
muestra un subrbol de un rbol-By la 7.3.4b muestra parte del mismo subrbol
cuando ste se altera por la insercin de 382. La hoja de la extrema izquierda ya esta-
430 480
ba llena, de manera que se dividen las cinco llaves 380, 382,395,406 y 412 en forma
tal que 380 y 382 se colocan en una nueva hoja izquierda, 406 y 412 en una nueva
hoja derecha, y la llave del medio, 395, avanza al nodo padre con apuntadores a las
hojas izquierda y derecha a cada lado. No hay problema en colocar 395 en el nodo
padre, dado que slo contienen dos llaves y tiene espacio para cuatro.
380 395 406 412 493 506 511
La figura 7.3.4c muestra el mismo subrbol con la insercin de las llaves 518
primero y luego 508. (El mismo resultado se alcanzara si fueran insertadas en orden
inverso.) Es posible insertar 518 en forma directa en la hoja de la extrema derecha,
dado que hay espacio para una llave adicional. Sin embargo, cuando llega 508, la (a) Porcin inicial de un rbol-B
hoja ya est llena. Las cinco llaves: 493, 506, 508, 511 y 518 se dividen de manera
que las dos ms pequeas (493y 506) se colocan en una nueva hoja izquierda, las dos
mayores (511 y 518) en una nueva hoja derecha y la llave del medio (508) avance
hacia el padre, que an tiene lugar para acomodarla, Obsrvese que la llave que avanza
hacia el padre siempre es la llave del medio, sin tomar en cuenta si llega primero o
despus que las otras.
Si el orden de un rbol-Bes par, tienen que dividirse las n - 1 llaves (exclu-
yendo la del medio) en dos grupos de tamao desigual: uno de tamao n/2 y otro de
tamao (n ~ 1)/2. [El segundo grupo siempre es de tamao (n - 1)/2, sin tomar
en cuenta sin es impar o par, dado que cuando n es impar (n - 1)/2 es igual a n/2]. 493 506 511
Por ejemplo, sin es igual 10, 10/2 (o 5) llaves estn en un grupo, 9/2 (o 4) estn en
el otro y una llave avanza, para un total de 10. Estas pueden dividirse de manera que
el grupo de mayor tamao siempre est en la hoja izquierda o en la derecha, o se.
pueden alternar las divisiones de manera que en una divisin, la hoja derecha con- (b) Tras la insercin de 382
tenga ms llaves y en la siguiente la izquierda contenga ms. En la prctica, no hay
mucha diferencia entre ambas tcnicas.
La figura 7.3.5 ilustra la polarizacin izquierda y derecha en un rbol-B de or-
den 4. Obsrvese que el elegir una rama derecha Q izquierda determina cul llave de-
be avanzarse hacia el padre. 395 430 480
Una base sobre la cual puede ser tomada la decisin en cuanto a dnde colocar
ms llaves en la hoja izquierda o derecha, es examinar los rangos de llaves bajo las
dos posibilidades. En la figura 7.3.5b, utilizando una polarizacin izquierda, el ran-
go de llaves del nodo izquierdo es de 87 a 102, o 15 y el rango de llaves en el nodo de-
recho es de 102 a 140 38. En la figura 7.3.5c utilizando una polarizacin derecha, 51 l 518
los rangos de llave son 13 (87 a 100) y 40 (100 a 140). As, seleccionaramos una pola-
rizacin izquierda en este caso, dado que la probabilidad de que un nuevo nodo vaya
a la izquierda o a la derecha es casi la misma, suponiendo que las llaves estn distri-
(e) Tras la insercin de 518 y 508
buidas de manera uniforme.
Hasta ahora, en la discusin anterior supusimos que hay espacio en el padre
para insertar el nodo del medio. Qu ocurre si el nodo padre tambin est lleno? Figura 7.3.4

444 Estructuras de datos en C Bsqueda 445


por ejemplo, qu pasa con la insercin de la figura 7.3.2c, si 71 tiene que insertarse
'clentro del nodo lleno L, y C, el padre de L, tambin est lleno? La solucin es bas-
tante simple. El nodo padre tambin se divide de la misma manera y su nodo del me-
dio avanza a su padre. Este proceso contina hasta que una llave sea insertada en un
nodo con espacio, o se divida el propio nodo-raz, A. Cuando esto ocurre, se crea un
nuevo nodo raz NR que contiene la llave que avanz de la divisin de A y tiene a las
dos mitades de A como hijos.
La figura 7.3.6 y la 7.3.7 ilustran este proceso con las inserciones de las figuras
23 61 74 90 100 106 152 186 194
7.3.2c y e. En la figura 7.3.6a, se divide el nodo L. (Suponemos una polarizacin iz-
quierda a lo largo de esta ilustracin). El elemento medio (75) debera avanzar hacia
e, pero C est lleno. As, en la figura 7 .3.6b, vemos que C tambin est siendo divi-
{ dido. Las dos mitades de L, son ahora hijos de las mitades correspondientes de C.
(a) Pequea rama ilicial de un rbol~B Setenta y cinco, que haba avanzando hacia C, tiene que avanzar ahora al nodo raz
. A, que tampoco tiene espacio. As, el propio A tiene que ser dividido, como se
muestra en la figura 7.3.6c. Por ltimo, la figura 7.3.6d muestra un nuevo nodo
>raz, NR, que contiene la clave que avanz de A y dos apuntadores a las dos mitades
de A.
La figura 7.3. 7 ilustra la subsecuente insercin de 22, como en la figura 7.3.2e.
En esa figura, 22 habra causado una divisin de los nodos G, By A. Pero, mientras
tanto, A ya ha sido dividido por medio de la insercin de 71, de manera que la inser-
cin de 22 procede como .en la figura 7.3.7. Primero se divide G y 20 avanza a B
(figura 7.3.7a), que a su vez se divide (figura 7.3.7b). Entonces 25 avanza a A 1, el
cual es el nuevo padre para B. Pero, dado que A I an tiene espacio ya no son nece-
sarias ms divisiones. Se inserta 25 en A 1 y la insercin est completa (figura
7.3.7c).
Como ilustracin final, la figura 7 .3.8 muestra la insercin de varias llaves en
el rbol-B de orden-5 de la figura 7.3.4c. Valdra la pena hacer una lista de llaves e
(b) Insercin de 102 con polarizacin iiquerda
insertar las mismas de manera continua en un rbol-B de orden-5 para ver cmo se
desarrolla.
Obsrvese que un rbol-B crece en profundidad a travs de la divisin de la raz
y la creacin de una nueva raz, y en anchura por medio de la divisin de los nodos.
As, la insercin en un rbol-B dentro de un rbol balanceado mantiene el balance.
Sin embargo, pocas veces es de '' arriba abajo'', dado que cuando se divide un nodo
no-hoja lleno las dos no-hojas que se crean son no-llenas. As, mientras el nmero
mximo de accesos para encontrar una llave es pequeo (dado que el rbol est
balanceado), el nmero promedio de tales accesos puede ser mayor que en un rbol de
"arriba abajo" en el cual los niveles superiores siempre estn llenos. En simula-
ciones, el.nmero promedio de accesos en la bsqueda dentro de un rbol d~ "arriba
23 61 186 194
abajo" aleatorio en realidad ha resultado levemente menor que en la bsqueda
dentro de un rbol-B aleatorio porque los rboles de "arriba abajo", aleatorios son
por lo general bastante balanceados.
(e) Insercin de 102 con polarizacin derecha Otro punto a tomar en cuenta en un rbol-Bes que las llaves antiguas (aquellas
que fueron insertadas primero) tienden a estar ms cerca de la raz que las llaves ms
jvenes, dado que han tenido ms oportunidad de avanzar. Sin embargo, es posible
Figura 7.3.5 que una llave permanezca en una hoja para siempre aun cuando con posterioridad se

446 Estructuras de datos en C Bsqueda 447


A
e

A 126 135 142 B

(a)

50 100 150

B ;
A B
135 142
75 D
CI C2

(a) Eliminacin de la Uave 113

(b) B

NR

AI t A2

ffi '"A
B CI C2, D E

/1\ 1\
J K LI L2 M B CI C2 D E

(e) (d)

Figura 7.3.6 (b) Eliminacin de la llave 120 y consolidacin

Figura 7.3.7

448 Estructuras de datos en C Bsqueda 449


J~O 540
inserten en su nodo llaves mayores y menores. Esto es distinto a lo que ocurre con un
/ / '------!---' ' '
/ ' ' rbol de "arriba abajo" en el cual una llave en un nodo ancestro tiene que ser ms
/ antigua que cualquier llave en un nodo descendiente.
Algoritmos para la insercin en un rbol-B

Como podra imaginarse, el algoritmo para la insercin en un rbol-B es bas-


'tante complicado. Para simplificar las cosas por el momento, asumiremos que pode-
mos accesar un apuntador al padre de node(nd) haciendo referencia afather(nd) y
la posicin del apuntador nd en node(father(nd)) mediante index(nd) de tal manera
que son(father(nd), index(nd)) es igual a nd. (Esto se puede implantar en forma
ms directa adicionando a cada nodo camposfather e index, pero esta forma de ac-
tuar tiene complicaciones que discutimos en breve). Suponemos tambin que r(p, i)
(a) Tras l.a insercin de 390, 476, y 350
es un apuntador al registro asociado a la llave k(p, i). Recurdese que l.a rutina/ind
da como resultado un apuntador a la hoja en la cual la llave debe insertarse y hace la
variable position igual a la posicin en la hoja donde la llave debe ser insertada. Re-
3~0 430 540 curdese tambin que key y rec son los argumentos llave y registro a ser insertados.
'' La funcin insert(key, rec, s, position) inserta un registro en un rbol-B. Sella-
'' ma despus de una llamada a find si el parmetro de salida found de esta rutina es
falso (es decir, la llave an no est en el rbol), donde el parmetros ha sido iguala-
do al apuntador al nodo que regres/ind (es decir, el nodo donde deben insertarse
ky y rec). La rutina usa dos rutinas auxiliares que sern presentadas en breve. La
primera de ellas, split, acepta cinco parmetros de entrada: nd, un apuntador al no-
do que ser dividido; pos, la posicin en node(nd) donde deben ser insertados una
llave y un registro; newkey y newrec, la llave y el registro insertados (esta llave y este
registro podran ser aquellos que avanzan de un nodo dividido con anterioridad o la
nueva llave y el nuevo registro insertados en el rbol); y newnode, un apuntador al
subrbol que contiene las llaves mayores que nwkey (es decir; 'uri apuntador a la mi0
tad derecha de un nodo dividido antes), que tierie que insertarse dentro del nodo
(b) Tras la insercin de 356 dividido en ese momento. Para mantener el nmero adecuado de hijos dentro de
un nodo, s tiene que insertar un nuevo apuntador hijo cada vez que un nuevo re-
gistro y una nueva llave se insertan dentro d un nodo. Cuando una nueva llave y un
320 430 540
nuevo registro se insertan enuna hoja, el apuntador al hijo se hace igual a null. Co-
/ / ~-'------"-./ '' rri una llave y un registro se insertan dentro de uno de los niveles superiores slo
// ', cando se divide un nodo en un nivel inferior, el apuntador al nuevo hijo que debe
ser insertado (newnode) apuntar la mitad derecha del nodo que fue dividido en el
nivl inferior. La mitad izquierda permanece intacta dentro del nodo del'nivel infe-
rior asignado antes. split dispone newkey y las llaves de node(nd) de manera que el
grupo de las n/2 llaves ms pequeas permanece en node(nd); la llave die! medio y
el registro son asignados a los parmetros de salida midkey y midrec, y las llaves res-
tantes se insertan dentro de un nodc nuevo, node(nd2), donde nd2 es tambin un
parmetro de salida.
La segunda rutina, insnode, inserta una llave newkey, el registro newrec y el
subrbol apuntado por newnode dentro de node(nd) en la posicin pos si hay espa-
cio. Recordar que la rutina maketree(key, rec) crea un nodo nuevo que contiene slo
(e) Tras la insercin de 462 la llave key, el registro rec y todos los apuntadores iguales a null. maketree da como
Figura 7.3.8 resultado un apuntador al nodo recin creado. Presentamos el algoritmo para insert
usando las rutinas splil, insnode y maketree.
450 Estructuras de datos en C Bsqueda 451
nd = s; i f (pos > ndiv2) 1
pos= position; /* newkey pertenece a node(nd2) *I
newnode = null; I* apuntador a la mitad derecha del nodo dividido copy(nd, ndiv2 + 1_, n- - 2, nd2);
newrec = rec; I* el registro que se va a insertar insnode(nd2, pos - ndiv2 - 1, newkey, newrec, newnode);
newkey = key; I* la llave que se va a insertar numtrees(nd) = ndiv2 + 1
f = fa ther( nd); midkey = k(nd, ndiv2);
while ( f l= null && numtree(nd) 1 == n) midrec = r(nd, ndiv2);
split(nd, pos, newkey, newrec,- newnode, nd2, return
I* fndeif */
newnode = nd2; i f (pos== ndiv2)
pos= index(nd); !* newkey es la llave de en medio * /
nd = f; copy(nd, ndiv2, n - 2, nd2);
f = fa ther( nd); numtrees(nd) = ndiv2 + 1;
newkey = mi_dkey; son(nd2, O)= newnode;
newreC = midrec; midkey ~ newkey;
I * fin de while */ midrec = newrec;
i f (numtrees(nd) < n) return;
insnode(nd po~, newkey, newrec, newnode)\ I* fin de if */
return; i f (pos < ndiv2)
findeif *I
I* /* newkey est en node(nd) *I
I* fes igual a null y numtrees(nd) es igual a n, as que nd es copy(nd, ndiv2, n - 2, nd2);
I* una raz completa; dividirla y crear. una nueva raz numtrees(nd) = ndiv2;
split(nd, pos, newkey, newrec, newnode, nd2, midkey, insnode(nd, pos, newkey, newrec, newnode);
tree = maketree(midkey, midrec); midkey = k(nd, ndiv2 - 1);
son(tree, D) nd; midrec= r(nd, ndiv2 - 1);
son(tree, 1) = nd2; return;
/* fin de if */
El ca.razn. de ese algoritmo es la rutina split que de hecl,o. divide al nodo. La
propia split usa una rutina auxiliar copy(ndl, first, tasi, nd2), que hace a una va- La rutina insnode(nd, pos, newkey, newrec, nwenode) inserta un nuevo
riable local numkeys igual a last-first + l y copia los campos de k(ndl, first) hasta registro newrec con llave newkey dentro de la posicin pos de un nodo no-lleno,
k(ndl, las/) dentro de k(nd2, O) hasta k(nd2,numkeys), los campos r(ndl, firsl) has- node(nd). newnode apunta a un subrbol que debe insertarse a la derecha del nuevo
ta r(ndl, last) (que contienen apuntadores a los registros reales) dentro de r(nd2, O) registro. Las claves restantes. y subrboles en las posiciones pos o mayores se
hasta r(nd2; numkeys) y los campos son(ndl ,first) hasta son(ndl, las/ + 1) dentro recorren una posicin. El valor de numlrees(nd) se incrementa en l. A continuacin
de son(nd2, 0) hasta son(nd2; numkeys + 1). copy tambin hace numtrees(nd2) presentamos un algoritmo para insnode:
igual a numtrees + 1. Si las/ < first, copy hace numtrees(nd2) igual a l pero no
cambia ninguno de los campos k,. ro son. split tambin usa getnode para crear un
1r--, .s V\_ \() oe:
for (i - numtrees(nd) - 1; i >= pos+ 1; i--) {
nuevo nodo e insnode para insertar una nueva llave dentro de un nodo no-lleno. son(nd, i-~ 1) = son(nd~ i);
Lo que sigue es un algoritmo para split. Las n llaves contenidas en node(nd) y k(nd, i) = k(nd, i - 1);
newkey tinene que distribuirse de manera que las n/2. menores se queden en r(nd, i) = r(nd, i - 1);
node(nd), las (n 1)/2 ms grandes (que es igual a n/2 sin es impar) sean coloca- /* fin de for *I
das en un nuevo nodo, node(nd2) y lal]ave media en midkey. Para evitar el volver a son(nd, pos+ 1) = newnode;
computar n/2 cada vez, suponer que su valor ha sido asignado a la variable global k(nd, pos) newkey;
ndiv2. El valor de entrada pos es la posicin en node(nd) en la cual newkey sera co-. r(nd, pos) = newrec;
locada si hubiera espacio para ello. numtrees(nd) +~ 1;

I * crear un nuevo nodo para la mitad derecha mantener la primera * Clculo de fathr e index
I* mitad en node(nd) * Antes de examinar la eficiencia del procedimiento de insercin, tenemos que
nd2 = getnode();
aclarr un punto pendiente: el relacionado con las funciones index y fa/her. Puede

452 Estructuras de datos en C Bsqueda 453


haberse notado que, aunque esas funciones se utilizan en el procedimiento insert y una vez que todos los nodos de un camino estn almacenados en la memoria interna,
hemos sugerido que ellas podran implantarse de manera ms directa agregando los' el padre de un nodo puede ser localizado mediante un simple examen del nodo pre-
campos index y father a cada nodo, esos campos no se actualizan por medio del al- vio en el cammo. As1, no hay necesidad de guardar y actualizar un campo Jather.
goritmo de insercin. Examinemos cmo puede alcanzarse esta actualizacin y por .Presentaremos, por lo t~nto, versiones modificadas defind e insert para locali-
qu elegimos omitir esa operacin. Despus examinamos mtodos alternativos de zar e msertar una llave en un arbol-B. Sea pathnode(i) una copia de i-simo nodo en
implantacin de las dos funciones que no requieren de la actualizacin. el camino de la raz a una hoja, loeation(i) su posicin (o bien un apuntador si el r-
Los camposjather e index tendran que ser actualizados cada vez que fuesen bol est en la memoria interna o bien una direccin de memoria externa si est en la
llamadas copy o insnode. En el caso de copy, tienen que ser modificados ambos memona externa) e index(i) la posicin del nodo entre los hijos de su padre (obsrve-
campos en cada hijo cuyo apuntador se copia. En el caso de insnode, tiene que ser se que index puede ser determinado durante el proceso de bsqueda y retenido para
modificado el campo index de cada hijo cuyo apuntador se mueve, tanto como am- su uso du:ante el proces~ de insercin). Nos referimos a son(i, j), k(i, j), y r(i, j)
bos campos en el hijo insertado. (Adems, los campos tienen que actualizarse en las como el1-es1mo campo h1Jo, llave y registro en pathnode(i) respectivamente. De ma-
dos mitades de un nodo raz dividido en la rutina insert). Sin embargo, esto . nera similar, numtrees(i) es el campo numtrees en pathnode(i),
impactara la eficiencia del algoritmo de insercin de una manera inaceptable en es- El siguiente ~lg.oritmo parafind utiliza la operacin aecess(i, loe) para copiar
pecial cuando tratemos con nodos en la memoria externa. En todo el proceso de bs- un nodo de la pos1c1on loe (ya sea de la memoria interna o externa) dentro de path-
queda e insercin en un rbol-B (excluyendo la actualizacin de los camposfathere node(1) Y el propio loe dentro de location(i). Si el rbol est almacenado en la me-
index), se accesan a lo sumo dos nodos en cada nivel del rbol. En la mayora de los m:oria interna esta operacin se realiza por medio de
casos, cuando no ocurre una divisin en un nivel, se accesa slo un nodo en el mis~
mo. Las operaciones copy e insnode, no requieren en ..realidad el acceso a los nodos .pathnode(i) nodefloc);
location(i) loe;
hijo movidos, aunque mueven nodos de un subrbol a otro, ya que lo hacen moviendo
apuntadores dentro de uno o dos nodos padre. El requerimiento de una actuali-
zacin de los campos.father e indexen aquellos hijos requerira del acceso y modifica- Si el rbol est almacenado en la memoria externa, la operacin se efecta con
cin de todos los propios nodos hijo. Pero la lectura y escritura de un nodo de y en la
directread(loc, pathnode(i.))
memoria externa son las operaciones ms costosas en todo el proceso de manejo de location(i) = loe;
un rbol-B. Cuando se considera que, en un sistema prctico de almacenamiento
de informacin, un nodo puede tener varios cientos de hijos, se hace claro que guar- donde direetread lee un bloque de memoria de una direccin externa particular (loe) a
dar los campos index y father dara por resultado que se reduzca en un ciento la efi- un buffer de la memoria interna (pathnode(i)). Tambin suponemos que nodese-
ciencia del sistema. areh(i, key) busca pathnode(i) en lugar de node(i). Presentamos el algoritmo/ind:
Entonces, cmo podemos obtener los datos father e index requeridos por el
proceso de insercin, sin guardar campos separados? Primero recurdese que la fun, q null;
cin nodesearch(p, key) da como resultado la posicin de.la llave menor en node(p) p tree;
que. sea mayor o igual que key, de modo que index(nd) es igual a nodesearch(fat- j -1;
her(nd), key). En consecuencia una vez que est disponible/a/her, puede obtenerse i -1;
index sin un campo aparte. while (p != null) {
Para entender cmo podemos obtener father, veamos un problema relaciona- index(++j) = i;
.do. Ninguna insercin en un rbol-B puede ocurrir sin bsqueda previa para locali- access( j, p);
zar la hoja donde tiene que insertarse la nueva llave. Esta bsqueda procede a partir i = nodesearch(j, key);
q = p
de la raiz y accesa un nodo en cada nivel hasta alcanzar la hoja apropiada. Es decir,
procede a travs de un solo camino, de la raz a una hoja. La insercin retrocede i f (i,< numtrees(,) - 1 && key k(j, i))
break;
despus a lo largo del camino, dividiendo todos los nodos llenos en el camino de la
p = son(j, i);
hoja a la raz; hasta alcanzar un nodo no-lleno en el que puede insertarse una llave
I * fin de while * /
sin una divisin. Una vez que se ejecuta esta insercin, el proceso termina. position = i;
El proceso de insercin accesa los mismos nodos que el de bsqueda. Como return(j); I* la llave est en pathnode), o ah pertenece *I
hab'amos visto el acceso de un nodo de la memoria externa es bastante costoso,
tendra sentido que el proceso de bsqueda guardara los nodos del camino que
recorre junto con sus direcciones externas en la memoria interna, donde el proceso , , El proceso de insercin est modificado en varios lugares. Primero, insnode y
de insercin los puede accesar sin una segunda operacin de lectura costosa. Pero eopy accesan pathnode(nd) en lugar de node(nd). Es decir, nd es ahora el ndice de

454 Estructuras de datos en C 455


dividido en lugar de node(nd2). Esto se puede hacer dado que el nodo en el rivel
un arreglo en lugar de un apuntador, de manera que todas las referencias a k, son, p
nd + 1 (si lo hay) del camino ya ha sido actualizado como se llama a split para nd
y numtrees significan referencias a campos dentro de un elemento de pathnode. Los
de m~ner~ que pathnode(dn + 1) puede ser usado otra vez. nd2 se mantiene com~
algoritmos para insnode y copy no tienen que cambiarse.
Segundo, split debe ser modificada para producir las dos mitades de un nodo la ub1cac1on actual del nuevo nodo (asignado por makenode). (Deberamos observar
que p~ede ser deseable res_ervar un camino a la llave recin insertada en pathnode si,
dividido. Esto supone una rutina replace(i), que remplaza el nodo en /ocation(i) con
por eiemplo, queremos eiecutar un recorrido secuencial o inserciones secuenciales
los contenidos de pathnode(i). Esta rutina es la inversa de access. Si el rbol est
. comenzando en ese punto. En ese caso, el algoritmo tiene que ajustarse de manera
guardado en la memoria interna, puede ser implantado mediante
adecu~d-~ para colocar la mitad izquierda o derecha apropiada del nodo dividido en
la pos1c10n md1cada de pathnode. Tampoco podemos usar pathnode(i + 1) para
node(location(i)) = pathnode(i); constrmr la mitad derecha pero, en su Jugar, debemos usar un nodo adicional de la
memona mterna. Dejamos los detalles al lector.)
y si est almacenado en la memoria externa mediante:
'I,ambin se modifica la rutina insert al usar nd - l en Jugar de Jather(nd).
Tamb1en llama a replace y makenode. Cuando se tiene que dividir Ja raz, ma-
directwrite(location(i), pathnode(i));
ketree construye un nuevo nodo raz en la memoria interna. Este nodo se coloca en
pathnode(i) (que ya no se necesita, dado que el antiguo nodo raz ha sido actualiza-
donde directwrite escribe un buffer en memoria (pathnode(i)) hacia un bloque de
do p~r split) Yextrae la e~critura usando makenode. A continuacin presentamos el
memoria externa en una direccin externa particular (location(i)). split usa tambin
algontmo revisado para msert:
una funcin makenode(i) que obtiene un nuevo bloque de memoria en la ubicacin
x, coloca pathnode(i) en ese bloque y da como resultado x. Lo que sigue es. una ver-
nd = s;
sin revisada de split: pos = position;
newnode = null;
if (pos> ndiv2) { newrec = rec;
copy(nd, ndiv2 + 1, n - 2,_ nd + 1); newkey = key;
insnode(nd + 1, pos - ndiv2 - 1, newkey, while (nd != O && numtrees(nd) == n) {
numtrees(nd) = ndiv2 + 1; split(nd, pos, newkey, newrec, newnode, nd2, midkey,
midkey = k(nd, ndiv2); midrec);
midrec = r(nd, ndiv2); newnode = nd2;
return; pos= index(nd);
/* lindeil *I nd--;
if (pos== ndiv2) newkey = midkey;
copy(nd, ndiv2, n 2, nd + 1); newrec = midrec;
numtrees(nd) = ndiv2; I * fin de while * /
son(nd + 1, O).= newnode; if (numtrees(nd) < n)
midkey = newkey; insnode(nd, pos, newkey, newrec, newnode).;
midrec = newrec; replace(nd,);
return; return;
} /* lindeif *I /*. fin de if */
if (pos< ndiv2) split(nd, pos, newkey, newrec, newnode, nd2, midkey, midrec);
copy(nd, ndiv2, n - 2, nd + 1); pathnode(O) = maketree(midkey, midrec);
numtrees(nd) = ndiv2; son(D, O) = na;
insnode(nd, pos, newkey, newrec, newnode); son(O, 1) = nd2;
midkey = newkey; tree = makenode(O);
midrec = newrec;
/* fin de if */
replace(nd); Eliminacin en rboles de bsqueda multivas
nd2 = makenode(nd + 1);

Obsrvese que nd es ahora una posicin en pathnode en Jugar de un apuntador ) . ~I mtodo ms simple para eliminar un registro de un rbol de bsqueda
a un nodo y que pathnode(nd + 1) se usa para construir la segunda mitad del nodo ' muit1v1as es conservar la llave en el rbol pero marcada de alguna manera que indi-

Estructuras de datos en Cs 457


456
que que representa un registro eliminado. Esto podra realizarse haciendo el apunta. Esta hoja se puede entonces compactar o liberar. En el peor caso, esto requerira
doral registro correspondiente igual a la llave nu/1 o asignando un campo indicado reescribir un nodo en cada nivel del rbol.
extra a cada llave para indicar s ha sido borrada o no. Por supuesto, el espacio ocu;': En un rbol-B estricto, tenemos que preservar el requerimiento de que cada
pado por el propio registro se puede usar otra vez. De esta manera, la llave queda en;' nodo contiene por lo menos n/2 llaves. Como ya observamos, si se est eliminando
el rbol como gua hacia los subrboles pero no representa un registro del archivo) una llave de un nodo no-hoja, su sucesor (que tiene que estar en una hoja) se mueve
La desventa1a de este enfoque es que el espacio ocupado por la llave en s se a la posicin eliminada y el proceso de eliminacin prosigue como s el sucesor fuese
gasta, conduciendo hacia nodos innecesarios cuando ya se han eliminado un gran eliminado del nodo hoja. Cuando una llave (ya sea la que debe ser eliminada o su su-
nmero de registros. Bits "eliminados" extra, requieren todava de ms espacio. cesor) se elimina de un nodo hoja y el nmero de llaves en el nodo disminuye por de-
Por supuesto, s un registro con una llave eliminada se inserta despus, el espa.) bajo de n/2, hay que hacer algo para remediar esta situacin. Esta situacin se llama
co para la llave puede reciclarse. En un nodo no-hoja, slo la misma llave podra subdesborde. Cuando ocurre, la solucin ms simple es examinar el hermano mayor
reutilizar el espacio, dado que es difcil determinar de manera dnmca que la llave o menor de la hoja. Si el hermano contiene ms de n/2 llaves, se puede adicionar la
recin insertada est entre el antecesor y el sucesor de la llave eliminada. Sn embar- llave ks en el nodo padre que separa a los dos hermanos al nodo con subdesborde y
go, en un nodo hoja (o, en ciertas stuacones, en una semhoja) el espacio de la llavt la ltima o primera llave del hermano (la ltima si el hermano es mayor y la primera
eliminada se puede. reusar por una llave vecina, dado que es muy fcil determinar la' si es menor) puede agregarse al padre en el lugar de ks. La figura 7.3.9a ilustra este
proximidad. Como una gran parte de las llaves estn en hojas o semhojas, s las proceso en un rbol-B de. orden-5. (Deberamos observar que una vez que el herma-
inserciones y elimnacones ocurren con la misma frecuencia (o si hay ms nser.?. no est siendo accesado, podemos distribuir las llaves de manera uniforme entre los
ciones que elmnaciones) y estn distrbudas de manera uniforme (es decir, las e]( dos hermanos en lugar de recorrer solo una llave. Por ejemplo, s el nodo con sub-
minaciones no estn agrupadas para reducir de manera sgnfcatva el nmero total desborde, ni contiene 106 y 112, la llave que separa en el padre/es 120 y el hermano
de llaves en el rbol de manera temporal) se puede perder espacio a cambio de la ven- n2 contiene 123, 128, 134, 139, 142 y 146 en un rbol-B de orden-?, podemos redis-
iaja que significa la facilidad de eliminacin. Hay tambin un pequeo gasto de ponerlas de manera que n I contenga 106, 112, 120 y 123, 128 se mueve a f como
tiempo en bsquedas posteriores, dado que algunas llaves requerirn que ms nodos separador y 134, 139, 142 y 146 se quedan en n2).
sean examinados si es que la llave eliminada no ha sdo nunca insertada en el primer Si ambos hermanos contienen exactamente n/2 llaves, ninguna llave tiene que
lugar. recorrerse. En ese caso el nodo con subdesborde y uno de sus hermanos se concate-
Si no queremos pagar este costo en cuanto a tiempo de bsqueda y espacio de nan o consolidan dentro de un solo nodo que tambin contiene la llave separadora
la eliminacin simplificada, hay tcnicas ms costosas de eliminacin que lo evitan. de su padre. Esto se ilustra en la figura 7.3.9b, donde combinamos el nodo con sub-
En un rbol de bsqueda no restringido, de multivas, se puede emplear una tcnica desborde con su hermano menor.
similar a la de eliminacin en un rbol de bsqueda binara: Por supuesto, es posible que el padre slo contenga n/2 llaves, de manera que
l tampoco tenga una llave extra qu ofrecer. En ese caso, la puede tomar de su
l. Si la llave a ser eliminada tiene un subrbol izquierdo o derecho vaco, elimi- padre y hermano como en la figura 7.3. 10a. En el caso peor, cuando los hermanos
nar slo la llave y compactar el nodo. S era la nica llave en dicho nodo, libe- del padre tampoco tienen llaves qu ofrecer, el padre y su hermano pueden tambin
rarlo, consolidarse y la llave puede ser tomada del abuelo. Esto se ilustra en la figura
2. Si la llave a ser eliminada tiene subrboles izquierdo y derecho no-vacos, 7.3. 10b. Potencialmente, si todos los ancestros no-raz de un nodo y sus hermanos
encontrar la llave que le sucede" (que debe tener un subrbol izquierdo vaco); de- contienen exactamente n/2 llaves, se tomar una llave de la raz, ya que las consoli-
jar que dicha llave tome su lugar y compactar el nodo que contena al sucesor. Si daciones ocurren en cada nivel desde las hojas hasta el nivel justo debajo de la raz.
el sucesor era la nica llave del nodo, liberarlo. Si la raz tena ms de una clave, el proceso termina con esto, dado que la raz de un
rbol-B no necesita tener ms de una llave. Si, por otro lado, la raz contena una
Dejamos el desarrollo del algoritmo y del programa detallado al lector. sola llave, esa llave se usa en la consolidacin de los dos nodos debajo de la raz, se
Sin embargo, este procedimiento puede dar como resultado un rbol que no libera la raz, el nodo consolidado se convierte en la nueva raz del rbol y se reduce
satisfaga los requerimientos para ser de "arriba abajo" un rbol-B, aun cuando el la profundidad del rbol-B. Dejamos al lector el desarrollo de un algoritmo real para
rbol original lo fuese. En un rbol de "arriba abajo", s la llave que est siendo eli- la eliminacin en un rbol-B segn esta descripcin.
minada es de una semi hoja que no es una hoja y la llave tiene subrboles izquierdo y Se debera observar sin embargo, que es tonto formar un nodo consolidado
derecho vacos, la semi hoja quedar con menos den - I llaves, aun cuando no sea con n - I llaves si una insercin posterior dividir de inmediato el nodo en dos. En
una hoja. Esto viola el requerimiento de un rbol de "arriba abajo". En ese caso, es un rbol-B de orden grande, puede tener sentido dejar un nodo con subdesborde con
necesario elegir un subrbol no-vaco aleatorio del nodo y mover la llave menor o la menos de n/2 llaves (aun cuando esto viole los requerimientos formales de un rbol-B)
mayor de ese subrbol a la semihoja de la cual fue eliminada la llave. Este proceso de manera que puedan realizarse inserciones futuras sin divisin. De manera tpica,
tiene que repetirse hasta que la semihoja de la cual se toma.una llave sea una hoja.

458 Estructuras de datos en C Bsqueda 459


.,..
o,
o

.
.
2:
m
::~ :t1\
----.e:---.. \ :g
5 :;
"o:
r,
~
o L. )'(' w
~
o.
3
t'] --
""
,e o 5

"'"' <
;::;
, "8.O
J:,~
e
; , '
..., -E- ~
o.
o ;;;
;::; ~ ; ---C'\ 1 ~

<=u
o
:: ,

- .
~

/ -r;
r, ro , _:
o N

~ -w N
"-
;
"O8.
~

"' ,
"'
"'
"'

ro
e,
2e
<1>
o.
"'
220 280

E f
D E A B C D
A B

l 73. 178 187 202

t t

JO 50 180 220 2SO

A H e A B e D E F

153 162 170 187. 202


72 80

(a) Eliminacin de 65, consolidacin y prstamo. (b) Eliminacin de 173 y una consolidacin doble .

.,.. Figura 7 .3.1 O

....o,

L
un nmero mnimo (mn menor que n/2) de llaves se define de manera que la cons<
lidacin ocurra slo si restan menos que mn llaves en un nodo hoja.

Eficiencia de rboles de bsqueda multivas


7-..'....---------------- ~
Las consideraciones primarias en la evaluacin de la eficiencia de rboles d
bsqueda multivas, como para todas las estructuras de datos, son el tiempo y el e
pacio. El tiempo se mide por el nmero de nodos accesados o modificados eh un.
operacin, antes que por el nmero de comparaciones de llaves. La razn para esto
es que, como ya se mencion, el acceso a un nodo involucra por lo general la lecfr~
en memoria externa y la modificacin de un nodo la escritura en memoria externa]
Esas operaciones consumen mucho ms tiempo que las operaciones en la memori
interna y dominan, por lo tanto, el tiempo requerido.
De manera similar, el espacio se mide por el nmero de nodos en el rbol y er :;;

tamao de los nodos en lugar de por el nmero de llaves que estn en realidad cante,'
nidas en los nodos, dado que se asigna el mismo espacio para un nodo sin importar'
las llaves que contenga. Por supuesto, si los propios registros se almacenan fuera de:
los nodos del rbol, el requerimiento de espacio para los registros est determinado .
"
por cmo est organizado su almacenamiento y no cmo est organizado el rbol en
s. Los requerimientos de memoria para registros sobrepasan, por lo general, los re-
querimientos para el rbol de llaves, de manera que el espacio real para el rbol
puede no ser significativo.
Primero examinemos rboles de bsqueda multivas de "arriba abajo". Supo-
niendo un rbol de orden-111 y n registros, hay dos posibilidades extremas. En el peor
caso para el tiempo de bsqueda, el rbol est des balanceado en su totalidad. Cual-
quier nodo excepto uno es una semi hoja con un hijo ycontiene m - 1 llaves. El ni-
co nodo hoja contiene ((n - 1) % (m - !)) + 1 llaves. El rbol contiene
."
((11 - l)l(m - 1)) + l nodos, uno en cada nivel. Una bsqueda o una insercin ac- ----<~
cesan ms de la mitad de los nodos en promedio y todos los nodos en el peor caso.
Una insercin requiere tambin de escribir uno o dos nodos (uno si la llave se inserta
en una hoja_, dos si tiene que crearse una nueva hoja;) Una eliminacin accesa
siempre todos los nodos y puede modificar uno slo, aunque en potencia, puede
modificar todos los nodos (a menos que una llave pueda simplemente marcarse
como eliminada).
En el mejor de los casos para el tiempo de bsqueda, el rbol est casi
balanceado, cada nodo excepto uno contiene m - 1 llaves y cada nodo no-hoja ex-
cepto uno tiene m hijos. Hay an ((11 - 1)/(m - 1)) + 1 nodos, pero hay menos de
lag,,, (11 1) + 1 niveles. As, el nmero de nodos accesados en una bsqueda, in-
sercin ci eliminacin es menor que este nmero. (En un rbol como ese, ms de la
mitad de las llaves estn en una semihoja o en una hoja, de manera que el tiempo
promedio de bsqueda no es mucho mejor que el mximo.) Por fortuna, como en el
caso de rboles binarios, es mucho ms frecuente el caso de rboles balanceados que
el de rboles desbalanceados, de manera que el tiempo de bsqueda promedio usan-
do rboles multivas es O(log n).
Sin embargo, un rbol de multivas general e incluso un rbol multivas de
"arriba abajo" usan una cantidad e.xcesiva de memoria. Para ver por qu esto es as,

462 Estructuras de datos en C


Bsqueda 463
ver la figura 7 .3. l l, que ilustra un rbol de "arriba abajo" de bsqueda multivias vo entre rboles multivias de "arriba abajo" rboles-B, dado que los rboles de
tpico de orden-l l con !00 llaves. El rbol es bastante balanceado Yel costo prome- t "arriba abajo" son por lo general bastante balanceados.
dio de bsqueda es ms o menos 2.10 [10 llaves en el nivel O requieren accesar un ' La insercin en un rbol-B requiere la lectura de un nodo por nivel y la escritu-
nodo, 61 en el nivel l requieren accesar dos nodos, y 29 en el nivel 2 requieren acec ra de un nodo como mnimo por nivel ms dos nodos para toda divisin que ocurra.
sar 3 nodos: (10 * 1 + 61 * 2 + 29 * 3)/100 = 2.19], que es razonable. Sin embargo, Si ocurren s divisiones, se escriben 2s + 1 nodos (dos mitades para cada divisin
para acomodar las 100 llaves, el rbol usa 23 nodos o 4.35 llaves por nodo, que ms el padre del ltimo nodo dividido). La eliminacin requiere de la lectura de un
representa un espacio utilizado de 43.5 por ciento. La razn para esto es que muchas. nodo por nivel para encontrar una hoja, escribiendo un nodo si la llave eliminada es-
hojas contienen slo una o dos llaves y la gran mayora de los nodos son hojas. t en una hoja y la eliminacin no causa subdesborde y dos nodos si la llave elimina-
Cuando aumenta el orden y el nmero de llaves, la utilizacin de espacio empeora, da est en un nodo no-hoja y la eliminacin de la llave que remplza supresin no
de manera que un rbol de orden-ll con miles de llaves puede esperar un 27 por causa que la hoja tenga un subdesborde. Si ocurre subdesborde, se requieren una
ciento de utilizacin y uno de orden-21 slo un 17 por ciento. Cuando el orden es lectura adicional (del hermano de cada nodo con subdesborde) por desborde, una
an mayor, la utilizacin cae a cero. Dado que se requieren rdenes grandes para escritura adicional para cada consolidacin excepto la ltima y tres escrituras adi-
producir costos de bsqueda razonables para grandes nmeros de llaves, los rboles ionales para el subdesborde final si no es necesaria una consolidacin (el nodo de
multivas de "arriba abajo" son una alternativa poco razonable para el almacena, , subdesborde, su hermano y su padre) o dos escrituras adicionales si es necesaria una
miento de datos. consolidacin (el nodo consolidado y su padre). Todas esas operaciones son O(log n).
Cualquier rbol-B est balanceado y cada nodo contiene por lo menos Como en el caso de un heap (seccin 6.3) y un rbol binario balanceado (sec-
(m - 1)/2 llaves. La figura 7.3.12 ilustra el mnimo y el mximo nmero de nodos y cin 7.2), la insercin y eliminacin del mnimo o mximo elementos son ambas
llaves en los niveles O, 1 y 2 y un nivel arbitrario i, tanto como el nmero mximoy i O(log n) en un rbol-B; por lo tanto, la estructura puede usarse para implantar una
mnimo total de llaves y nodos en un rbol-B de orden m y nivel mximo d. En esa cola de prioridad (ascendente o descendente) de manera eficiente. De hecho, un
figura, q es igual a (m - 1)/2. Advirtase que el nivel mximo es 1 menos que el n.- rbol 3-2 (un rbol-B de orden-3) es probable que sea el mtodo prctico ms eficien-
mero de niveles (dado que la raz est en el nivel 0), de manera que d + 1 es igual al te para implantar una cola de prioridad en la memoria interna.
nmero mximo de accesos de nodos necesarios para encontrar un elemento. Par- Como cada nodo en un rbol-B (excepto la raz) tiene que estar, de manera
tiendo del mnimo nmero total de llaves de la figura 7.3.12, podemos deducir que el aproximada, medio lleno, el peor caso de utilizacin de memoria se aproxima a 50
nmero mximo de accesos para una de las llaves nen un rbol-B de orden mes 1 + por ciento. En la prctica la utilizacin promedio de memoria en un rbol-B se apro-
log + 1 (n/2). As, a diferencia de los rboles de multivas de "arriba abajo" el n- xima al 69 por ciento. Por ejemplo, la figura 7.3.13 ilustra un rbol-B de orden-ll
meio mximo de accesos a nodos crece slo de manera logartmica con el nmero de con las mismas 100 llaves que el rbol de acceso mltiple o multivas de la figura
llaves. No obstante, corno sealamos antes, el tiempo promedio de bsqueda es 7.3.11. El tiempo de bsqueda promedio es de 2.88 (una llave que requiere un acceso
de nodo, 10 llaves que requieren 2 accesos y 89 que requieren 3) que es mayor q\le el
del rbol de multivias correspondiente. En realidad, un rbol multivas de "arriba
abajo" bastante balanceado tendr menor costo de bsqueda que un rbol-B, dado
Mnimo Mximo que todos sus nodos superiores estn siempre llenos por completo. Sin embargo, el
rbol-B contiene slo 15 nodos, produciendo una utilizacin de memoria del 66.7
Nivel. Nodos Llaves Nodos Llaves por ciento, mucho ms elevada que la utilizacin de 43.5 por ciento del rbol
multivas.
o m-1
Perfeccionamiento del rbolB
2 l, m (m- l)m

Existen muchas formas de perfeccionar la utilizacin de memoria de un rbol-B.


2 2(q + !) 2q(q+I) m2 (m- l)m
2 Un mtodo es retrasar la divisin de un nodo cuando ocurre desborde. En lugar de
ello, se distribuyen de manera uniforme las llaves del nodo y uno de sus hermanos
adyacentes, as como la llave en el padre que separa los dos nodos. Esto se ilustra en
I 2(q + 1i- 1 2q(q + 1i- 1 m' (m - 1 )11/
la figura 7.3.14 en un rbol-B de orden-7. Cuando tanto un nodo como su hermano
1 + 2(2 + )" - !)
md-.1_ 1 estn llenos, los dos nodos se dividen en tres. Esto garantiza una utilizacin de
Total 2(q + 1)d
q m-1 memoria mnima de casi 67 por ciento, aunque la utilizacin de memoria es, en reali-
dad, mayor en la prctica. Tal rbol se llama rbol-B*. En efecto, esta tcnica se
Figura 7.3.12 puede extender an ms redistribuyendo las llaves entre todos los hermanos y el

464 Estructuras de datos en C Bsqueda 465


12 130)

(a) Arbol-B original de orden-7.


;;;

50

(,.__1_0_2_0__3
__3_5__4__ _) 100 1!O 120 130).
(b) Tras la insercin de 35 y redistribucin de hermanos.

50

oe,
M

80 100 1 !O 120 130 )


-....._10__1_5_2_0__3___
3_5__
4_~)

(e) Tras la insercin de 15 y 80.


o 00
e,
~

00
n

35 90
"
00
e'
o

,5,
"

!O 15 20 30 40506080) e 100 110 120 130)

(d) Tras la insercin de 90 y redi_stribuciil de padre y hermanos.

Figura 7.3.14 Arbol-B de orden-7 con redistribucin de nodos mltiples

466 Estructuras de datos en C 8(.squeda 467


padre de un nodo lleno. Desafortunadamente, este mtodo exige su propio precio,
dado que requiere de accesos adicionales muy costosos en inserciones con desborde,
mientras que la utilizacin de espacio adicional marginal alcanzada, considerando
cada hermano extra, se vuelve cada vez ms pequea.
Otra tcnica es usar un rbo/-8 compacto. Un rbol-B de ese tipo tiene una uti- , M
"'o ~
M
~
M 00
lizacin de memoria mxima para un orden y un nmero de llaves dados. Se puede/ "'
~ ~ 00
"' "'
mostrar que esta utilizacin mxima de memoria para un rbol-B de orden y nmero ;;; ~
o e,
~
M "'
"'
de llaves dados se logra cuando los nodos del fondo del rbol contienen tantas llaves
~ ~ 00
"' "'
N 00
como sea posible. La figura 7.3.15 ilustra un rbol-B compacto para las 100 ~
00
"'"'
~

llaves de las figuras 7. 3.11 y 7. 3. 13. Se puede mostrar que el costo de bsqueda pro-
~ 00
"' "'
~ o
medio para un rbol-B compacto nunca es mayor que el costo promedio mnimo ;;; ;;; 00
00 ~

~
"' "'
~ 00
"'
ms 1 entre todos los rboles-B con el orden y el nmero de llaves dados. As, ade- ~

ms de que un rbol-B compacto alcanza una utilizacin de memoria mxima, al-


00
,a
~
00
M
,a
~
~
00
"'
11:
~
"'
canza tambin un costo de bsqueda razonable. Por ejemplo, el costo de bsqueda e,
,a
,a
~
~
~ "'
~
o ~ 00
para el rbol de la figura 7.3.14 es slo 1.91 (9 llaves en el nivel O, que requieren un "'"'
~
"'
acceso, y 91 llaves en el nivel l, que requieren dos accesos: 9 * 1 + 91 2 = 191/100 ~ e>
"'
= 1.91), que est muy cerca del ptimo. Sin embargo el rbol, usa slo 11 nodos, ;:
M
~
~

"' "
~

00 M ;; ,a
para una utilizacin de memoria del 90.9 por ciento. Con ms llaves, la utilizacin M M

de memoria en rboles-B,-compactos alcanza 98 por ciento e incluso 99 por ciento:


~
"' ~

e, M
Desafortunadamente, no se conoce un algoritmo eficiente para insertar una ~ "'
M M
~
~
"'
llave en un rbol-B compacto y mantener su tamao. En lugar de ello, las inser, ~
o e, o
00 :;; M
dones proceden como en un rbol-B ordinario y su estabilidad no se mantiene. De "' "
~
"'
~

..;
manera peridica (por ejemplo, de noche, cuando no se usa un archivo), puede ser ;;; ...:
construido un rbol-B compacto a partir del rbol no-compacto. Sin embargo, un " ~
rbol-B compacto se degrada tan rpidamente cuando ocurren inserciones que, para ,a o ~ =
"'""
~
~ 00
M "'e, ~ "' "'
:
rdenes elevadas, la utilizacin de memoria decae por debajo de la de un rbol-B M " ~
aleatorio despus de menos del 2 por ciento de llaves adicionales tienen que ser inser- "'
~
"""
M
"'""e, :! """"
tadas. Tambin, el nmero de divisiones requeridas por una insercin es superior en
M

"'e, "'
o e,
promedio que para un rbol-B aleatorio. As, un rbol-B compacto debera ser usa-
M
~ "'e,
"" "'
:!
"'"
00
M

do slo cuando el conjunto de llaves es bastante estable. ~


e,
"' M
M "'
"'
:;;
e, :! 00
Una tcnica ampliamente usada para reducir tanto tiempo como espacio "' M

requeridos en un rbol-Bes usar varias tcnicas de compresin de las llaves. Como ""
~

"'
M
e, "'
~
M "'e, e, M

"'
todas las llaves en un nodo dado son bastante similares entre s, los bits iniciales de "
dichas llaves es muy probable que sean los mismos. As, esos bits iniciales pueden ~

;; "'
e, "'e, ~

guardarse de una vez para el nodo entero (o peden ser determinados como parte del
M
M
" "'
e,
proceso de bsqueda a partir de la raz del rbol observando que si dos llaves adya- ::: e,
~
M ~ "'
"' M e, "'
centes en un nodo tiene el mismo prefijo, todas las llaves en el subrbol entre las dos
llaves tienen tambin el mismo prefijo). Adems, todos los bits que preceden al pri-
N
;:
oe,
M
"'e,
N "'e,
mer bit que distingue una llave de su vecina anterior no tienen que ser retenidos
(aunque s tiene que serlo una indicacin de su posicin). Esto se llama compresin
~
o o
M
"' "'N
N s
~
~

frontal. Una segunda tcnica llamada compresin final, mantiene slo lo suficiente
" e,
o "'
"' ""
s ~

de la parte posterior de la llave para distinguir entre una llave y su sucesora. "' '
Por ejemplo, si tres llaves son anchor andrew y antoin, andrew se puede codifi-
car como 2d indicando que los dos primeros caracteres son los mismos que su prede-
cesor y que el siguiente carcter, d, distingue la llave de su predecesora y sucesora. Si

468 Estructuras de datos en C Bsqueda 469


los sucesores de andrew dentro del nodo fuesen andu!e, antoin, append y app!es,
andrew sera codificado como 2d, andu/e como 3u, antoin como 2 y append como
" lppe.
Si se usa compresin final, es necesario accesar el registro mismo par,a determi-
" nar si una llave est presente en el archivo, dado que la llave completa no puede re-
construirse a partir de la codificacin. Tambin bajo ambos mtodos, el cdigo de la
,llave que se retiene es de longitud variable, de manera que el mximo nmero della-
ves en un nodo ya no est fijo. Otra desventaja de la codificacin de llaves con longi-
tud variable es que la bsqueda binaria ya no puede usarse para localizar una llave
dentro de un nodo. Adems, el cdigo de la llave para algunas llaves existentes tal
vez tenga que ser cambiado cuando se inserta una nueva llave. La ventaja de la
compresin es que permite que sean guardadas ms llaves en un nodo, de manera
que la profundidad del rbol y el nmero de nodos requeridos puede ser reducido.

"'a
Arboles-e+

Una de las desventajas principales del rbol-Bes la dificultad de recorrer las


llaves en orden secuencial. Una variacin de la estructura bsica de un rbol-B, el
00
~
' +,
rbol-B retiene la propiedad de acceso aleatorio rpido del rbol-B, mientras que
e,
-O tambin permite el acceso secuencial rpido. En el rbol-B +, todas las llaves se guar-
~
' dan en hojas y se copian en nodos no-hoja para poder definir caminos para la locali-
M
00 zacin de registros individuales. Las hojas se ligan para proporcionar un camino
secuencial para recorrer las llaves en el rbol.
La figura 7.3.16 ilustra un rbol-B+. Para localizar el registro asociado con la
llave 53 (acceso aleatorio), se compara primero la llave con 98 (la primera llave en la
raz). Como es menor, se procede con el nodo B. Cincuenta y tres es despus compa-
rada con 36 y luego con 53 en el nodo B. Como es menor o igual que 53 se procede al
hado E.
Obsrvese que la bsqueda no culmina cuando se encuentra la llave como en el
cas de un rbol-B. En un rbol-B est contenido, con cada llave del rbol, un apun-
tador al registro correspondiente, ya sa en una hoja o un nodo no-hoja. As, una
vez encontrada la llave puede ser accesado el registro. En un rbol-B + los apuntado-
res a registros estn asociados a las llaves slo en nodos hojas; como consecuencia,
la bsqueda no est completa hasta'que se localice la llave en una hoja. Por lo tanto,
cuando se obtiene igualdad en un nodo no-hoja la bsqueda contina. En el nodo E
(una hoja) se localiza la llave 53, y de ella el registro asociado en la llave. Si quere-
mos ahora recorrer las llaves en el rbol en orden secuencial comenzando con la llave
53, slo necesitamos seguir los, apuntadores en los nodos hoja.
La lista ligada de hojas se llama conjunto secuencial. En las implantaciones
reales, los nodos del conjunto secuencial no contienen con frecuencia todas las llaves
del archivo. En lugar de ello, cada nodo del conjunto secuencial sirve como ndice a
un rea de datos grandes donde se guardan un gran nmero de registros. Una bs-
queda involucra el' recorrido de un camino en el rbol-B +, leyendo un bloque del
rea de datos asociada al nodo hoja que es accesada al final y despus buscar en el
bloque el registro requerido de manera secuencial.

470 Estructuras de datos en C Bsqueda 471


El rbol-B + puede ser considerado como una extensin natural del archiv
secuencial indexado de la seccin 7. l. Cada nivel del rbol es un ndice al nivel que
Keys
sucede y el nivel menor, el conjunto secuencial, es un ndice al propio archivo.
180
La insercin en un rbol-B + procede de manera muy similar a la insercin en 185
un rbol-B excepto que, cuando se divide un nodo, la llave del medio permanece en !867
la mitad izquierda y es promovida al padre. Cuando se elimina una llave de una hoc 195
207
ja, sta puede retenerse en los nodos no-hojas, dado que es an un separador vlid 217
entre las llaves en los nodos de abajo. 2t74
El rbol-B+ retiene la eficiencia en la bsqueda e insercin del rbol-E pero'. 21749
217493
incrementa la eficiencia del hallazgo del registro siguiente en el rbol de O(log n) (e 226
un rbol-E, donde encontrar el sucesor involucra subir o bajar en el rbol) a O(l) 27
(en un rbol-B +, donde se accesa una hoja adicional a lo sumo). Una ventaja adi; 274
278
cional del rbol-B+ es que no se necesita guardar apuntadores a registros en l<f 279
nodos no-hojas, lo que incrementa el orden potencial del rbol. 2796
281
284
Arboles de bsqueda digitales 285
/,
286
Otro mtodo de usar rboles para apresurar una bsqueda es formar un rbol 287
288
general basado en los smbolos de los que estn compuestas las llaves. Por ejemplo, 294
si las llaves son enteros, cada posicin de dgito determina uno de diez hijos posibles 307
de un nodo dado. Un bosque que representa uno de esos conjuntos de llaves., 768
ilustra en la figura 7.3.17. Si las llaves constan de caracteres alfabticos, cada letr.a
del alfabeto determina una rama en el rbol. Advirtase que todo nodo hoja con/
tiene el smbolo especial eok, que representa el final de una llave. Una hoja tal dele
contener tambin un apuntador al registro que se est almacenando.
Si se representa un bosque por medio de un rbol binario, como en la seccin
5.5, cada nodo del rbol binario contiene tres campos: symbol, que contiene un. 1
smbolo de la llave; son, que es un apuntador al hijo mayor del nodo en el rbolori'.
ginal; y brother, que es un apuntador al hermano siguiente ms joven en el rbol
original. El primer rbol en el bosque est apuntado por un apuntador externo tree,
y las races de los otros rboles en el bosque estn ligadas en una lista lineal mediante
el campo brother. El campo son de una hoja en el bosque original apunta a un re7
gistro; la concatenacin de todos los smbolos _en el bosque original en el camino de
nodos de la raz a la hoja es la llave del cegistro, Hacemos dos estipulaciones adis
cionales que aceleran el prnceso de bsqueda e insercin para un rbol de ese tipo;
cada lista de hermanos est dispuesta en orden. ascendente en el rbol binario segn
el campo symbol, y se considera que el smbolo eok es mayor que cualquier otro...
Usando esta representacin de rbol binario, podemos presentar un algoritmo
para buscar e insertar en un rbol digital no vaco de ese tipo. Como es usual, key es
la llave para la cual se realiza la bsqueda, y reces el registro que queremos insertar
si no se encuentra la llave key. key(i) es el i-simo smbolo de la llave. Si la llave tiene
11 smbolos, suponemos que key(n) es igual a eok. El algoritmo usa la operacinget,
11ode para asignar un nuevo nodo. al rbol cuando sea necesario. Suponemos que
recptr es un apuntador al registro rec que ser insertado. El algoritmo da co_mo -
resultado un apuntador al registro que se est buscando y usa una funcin auxilia,
i11ser1, cuyo algoritmo tambin est dado.
Figura 7 ,3.17 Un bosque que representa una tabla de llaves.

Estructuras de datos ene 473


472
p ""' tree; do el smbolo '7', la nica llave que puede coincidir es 768. De manera similar, al
father = null; I* father es el padre de p *! reconocer los dos smbolos '1' y '9' la nica llave donde puede haber coincidencia es
for ( i = o; ; i++) { 195. As, el bosque de la figura 7.3.17 se puede abreviar como el de la figura 7.3.18.
q = null; I* q apunta al otro hermano de p *! En esa figura un cuadrado indica una llave y un crculo un nodo del rbol. Se usa
while (p != null && symbol(p) < key(i)) { lnea punteada para indicar un apuntador de un nodo del rbol a una llave.
q = p; Hay algunas diferencias significativas entre los rboles de las figuras 7.3.17 y
p = brother(p); 7.3.18. En la 7.3.17, un camino de la raz a una hoja representa una llave entera; as
I * .fin de whlle * /
no hay necesidad de repetir la propia llave. En la figura 7.3.18, sin embargo, una lla-
i f (p == null 11 symbol(p) > key( i)) {
insval = insert(i, p};
ve puede ser reconocida con slo algunos de sus smbolos iniciales. En aquellos casos
return(insval); en que se busca una llave que se sabe est en la tabla, el registro que corresponde a
/* fin de if */ esa llave puede ser accesado al encontrar una hoja. Si, no obstante, como es ms
i f (key(i) == eok) probable, no se sabe si la llave est en la tabla, se tiene que confirmar que la llave es
return(son(p)); en efecto la correcta. As, la llave completa tiene que guardarse tambin en
else { el registro. Adems, en la figura 7.3.17 puede reconocerse un nodo hoja en el rbol
father = p; porque sus contenidos son eok. As, su apuntador son puede usarse para apuntar al re-
p = son(p); gistro que esa hoja representa. Sin embargo, un nodo hoja de la figura 7.3.18 puede
I * fin de else */ contener cualquier smbolo. As, al usar el apuntador son de una hoja para apuntar
/* fin de for *I al registro, se requiere un campo extra en cada nodo o para indicar si el
nodo es o no una hoja. Dejamos la representacin del bosque de la figura 7.3.18 y la
El algoritmo para insert es el siguiente: implantacin de un algoritmo de bsqueda e insercin como ejercicio para el lector.
La representacin de rbol binario de un rbol de bsqueda digital es eficiente
/* inSertar el i-simo smbolo de la llave */
s = getnode()
cuando cada nodo tiene pocos hijos. Por ejemplo, en la figura 7 .3. I 8, slo un nodo
symbol(s) = key(i); tiene hasta seis (de entre 10 posibles) mientras que la mayora tiene slo uno, dos o
brother( s) = p; tres hijos. As, el proceso de bsqueda en la lista de hijos para encontrar coinciden-
i f (tree == null) cia con el siguiente smbolo de la llave es relativamente eficiente. Sin embargo, si el
tree = s; conjunto de llaves es denso dentro del conjunto de todas las llaves posibles (es decir,
else si casi cualquier combinacin posible de smbolos.aparece en realidad en una llave),
i f (q != null) la mayora de los nodos tendr un gran nmero de hijos y el costo del proceso de
brother( q) s; bsqueda se vuelve prohibitivo.
else
(father == null) ? tr.ee = s : son(father) s
/* insertar los smbolos restantes de la llave *'/
' Tries
for (j = i; key(j) != eok; j++) {
father = s; Un rbol de biisqueda digital no tiene que ser implantado necesariamente
s = getnode(); como un rbol binario. En lugar de ello, cada nodo en el rbol puede contener m
symbol(s) = key(j + 1); apuntadores que correspondan a los m smbolos posibles en cada posicin de la llave.
son(father) = s; As, si las llaves fueran numricas, habra 10 apuntadores en un nodo, y si fueran
brother(s) = null; estrictamente alfabticas, habra 26. (Tambin podra haber adems un apuntador
I * fin de for * / extra correspondiente a eok, o un indicador con cada apuntador para denotar que
son(s) = addr(rec); apunta a un registro y no a un nodo del rbol). Un apuntador en un nodo est aso-
return(son(s));
ciado a un valor particular de smbolo basado en su posicin en el nodo; es decir, el
primer apuntador corresponde al valor de smbolo inferior, el segundo al segundo
Observese que guardando la tabla de llaves como un rbol general, slo necesi- inferior y as de manera sucesiva. En consecuencia es innecesario guardar los pro-
tamos buscar en una lista pequea de hijos para encontrar si un smbolo dado aparece pios valores de smbolos en el rbol. El nmero de nodos que tiene que ser accesado
en una posicin dada dentro de las llaves de la tabla. Sin embargo, es posible hacer el para encontrar una llve particular es log mn. Un rbol de bsqueda digital implan-
rbol an ms pequeo eliminando aquellos nodos desde los cuales slo una hoja tado de esta manera se llama un trie (de la palabra en ingls retrieva!, recuperacin).
puede ser alcanzada. Por ejemplo, en las llaves de la figura 7.3.17, una vez reconoc-

474 Estructuras de datos en Bsqueda 475

J
Un trie es til cuando el conjunto de llaves es denso, de manera que la mayora
de los apuntadores en cada nodo se usan. Cuando el conjunto de llaves es escaso, un
trie gasta una gran cantidad de espacio con nodos muy grandes que estn vacos en
su mayor parte. Si se conoce de antemano el conjunto de llaves en un trie y ste no
cambia, hay un sinnmero de tcnicas para minimizar los requerimientos de espa-
cio. Una tcnica es establecer un orden diferente en el cual se usan los smbolos de
o . una llave para la bsqueda (de manera que, por ejemplo, el tercer smbolo de la llave
1 1 1
argumento pueda ser usado para accesar el apuntador apropiado en la raz del trie,
el primer smbolo en los nodos del nivel 1, y as de manera sucesiva.) Otra tcnica es
8 8 ,:671 permitir que los nodos del trie se traslapen unos a otros, de manera que apuntadores
ocupados de un nodo recubran apuntadores vacos de otro.

0 1 .

8 7.3.1. Muestre cmo puede uS'arse un rbol-By un rboI-B+ para imJ)lanir UriaCoia de
prioridad (Ver secciones 4: I y 6.3). Mostrar que una secuencia de ,flnserciones y ope-
raciones de eliminacin mnimaspuede ser ejecutada en O(n log n) pasOs. Escribir
rutinas en C para insertar y eliminar en unacola de prioridadiroplantada pormedio
de un rbol 2-3.
7 .3.2. Elija cualquier prrafo grande de un libro. Insertar cada palabra del prrafo, en or-
den, dentro de un rbol de bsqueda multivas de "arriba abajo" inicialmentevaco
de ordef5, omi_tiendo cualquier duplicado. Hacer lo mismo para un rbol-B de
orden-5, un rbol-B + de orden-5 y un rbol de bsqueda digital.
7.3.3. Escriba rutinas en lenguaje C para implantar las operaciones de insercin de un suce-
sor en un rbol-B si el rbol-B se guarda en:
a. memoria interna.
b. memoria externa de acceso directo.

~) '0 7.3.4. Escriba un algoritmo y una rutina en lenguaje C para eliminar ur registro de un rbol
de bsqueda multivas de "arriba abajo" de orden n.
7.3.5. Escriba un algoritmo y una rutin.a en lenguaje C para eliminar un registro de un rbol-

86J~~~~ B de ord~n n.
7.3.6. Escriba un algoritmo para crear un rbol-B compacto a partir de una entrada ordena-
da. U:Sar el algoritmo para escribir una rutina en lenguaje C para producir un rbol-B
compacto a partir de un rbol-B ordinario.
7.3.7. Escriba un algoritmo y una rutina en lenguaje C para realizar-uTla,,~squeda en un
rbolB.
7.3.8. Escriba una rutina en. lenguaje C y un algoritmo para:
a. insertar en un rbol-B +

0 1
1
b. insertar en un rbol-B*
c. eliminar de un rbol-B +
d. eliminar de un rbol-B*
7.3.9. Cuntos rboles 2-3 diferentes que contengan los enteros del I al 10 se pueden cons-

B
Figura 7.3.18 Un bosque condensado que representa una tabla de llaves.
truir? Cuntas Permutaciones de esosenteros resultan en cada rbol si se insertan en
un rbol al inido' vaco en orden de permutacin?
7.3. to. Desarrolle algoritmos para buscar e insertar en un rbol-B que usen compresin fron-
tal y final.

476 Estructuras de datos en C Bsqueda 477


7 .3.11. Escriba un algoritmo de bsqueda e insercin y una rutina en lenguaje C que lo realiM remos el desarrollo de mtodos que estn prximos al ideal, y determinar qu hacer
ce para el bosque de bsqueda digital de la figura 7.3.18. cuando no se alcanza el ideal.
7 .3.12. Muestre cmo implantar un trie en la memoria externa. Escriba -una rutina de bsM Consideremos el ejemplo de una compaa con un archivo inventario en el cual
queda e jnsercin en lenguaje C para un trie.
cada registro tiene como llave un nmero de pieza de siete dgitos. Supngase que la
compaa tiene menos de 1000 piezas y que hay un solo registro para cada pieza; En-
tonces un arreglo de 1000 piezas es suficiente para contener el archivo completo. El
7.4. DISPERSION arreglo est indexado por enteros del Oal 999 inclusive. Los ltimos tres dgitos del
nmero de pieza se usan como ndice para el registro de la pieza en el arreglo. Esto
En las dos secciones precedentes supusimos que el registro estaba guardado en una est ilustrado en la figura 7.4.1. Obsrvese que dos llaves que estn relativamente
tabla y que era necesario pasar a travs de cierto nmero de llaves antes de encontrar cerca la una de la otra en orden numrico, como 4618396 y 4618996 pueden estar
la deseada. La organizacin del archivo (secuencial, secuencial indexada, rbol 01s alejadas entre s en la tabla que dos llaves muy separadas tales como 0000991 y
binario, etc.) y el orden en el que se insertan las llaves afectan el nmero de llaves a 9846995. Slo los ltimos tres dgitos de la llave se usan para determinar la posicin
inspeccionar antes de obtener la deseada. Es obvio que las tcnicas de bsqueda efi- de un registro.
cientes son aquellas que hacen mnimo el nmero de comparaciones. De manera p- Una funcin que transforme una llave dentro del ndice de una tabla se llama
tima, desearamos tener una organizacin de la tabla y una tcnica de bsqueda en la una funcin de dispersin. Si h es una funcin de dispersin y key es una llave,
c.ual no hubiesen comparaciones innecesarias. Veamos si esto es viable. h(key) se llama la dispersin de llave y es el indice en el cual un registro con la llave
Si cada llave debe recuperarse en un solo acceso, la localizacin del registro
dentro de la tabla puede depender slo de la llave; nq puede depender de la localiza-
cin de las otras llaves, como yn un rbol.La manera ms eficiente de organizar una
Posicin Llave Registro
tabla de ese tipo es como un arreglo (es decir, cada registro se guarda en un desplaza-
miento especfico de la direccin base de la tabla), Si los registros son enteros, las o 4967000
1
propias llaves pueden servir como ndices al arreglo.
2 8421002
Consideremos un ejemplo de un sistema de ese tipo. Suponer que una 3
compaa de manufactura.tiene un archivo de inventarios que consta de 100 piezas,
cada una con un nmero de pieza nico de dos dgitos. Entonces la manera obvia de
almacenar este archivo es declarando un arreglo 395
396 4618396
parttype part[1DDJ; 397 4957397
398 .
donde part[i] representa el registro cuyo humero de pieza es i. En esta situacin, los 399 1286399
n111eros de pieza son llaves que se usan como ndices del arreglo. Au.n cuando la 400
compaa tenga existencias de menos de 100 piezas, se puede usarla misma estructu- 401 . .

ra para guardar el archivo inventario. Aunque muchas localizaciones enpart pueden


corresponder a llaves no existentes, este gasto est compensado por la ventaja del ac-
ceso directo a cada una de las piezas existentes. . .
Desafortunadamente, sin embargo, tal sistema no es siempre prctico. Por 990 0000990
ejemplo, suponer que la compaa tiene un archivo inventario de ms de 100 . 991 0000991
artculos y que la llave de cada registro es un nmero de pieza de siete dgitos. Enton- 992 1200992
ces se requerira de un arreglo de 10 millones de elementos para usar la indexacin 993 0047993
directa con la llave entera de siete dgitos. Esto desperdicia una. cantidad de espacio 994
inaceptable porque es muy improbable que una compaa surta ms. de unos pocos 995 9846995
miles de piezas. 996 4618996
Lo que es necesario es algn mtodo para convertir una llave en un entero 997 4967997
dentro de un rango limitado. De manera ideal, dos llav.es no deberan ser convertidas 998
999 0001999
en el mismo entero. Sin embargo, t;1l mtodo ideal por lo regular no existe. Intenta- Figura 7 .4.1

478 Estructuras de datos en C Bsqueda 479

You might also like