Escolar Documentos
Profissional Documentos
Cultura Documentos
YEDIDY AH LANGSAM
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
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.
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
.
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 = 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.
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. '
/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;
__ __
,, ,_,,, --"'
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;
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
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)
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.
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.
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.
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.
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
!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]
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;
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
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-
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
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
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 {
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
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;
D
.
e
Pilas B
A
Figura 2.1.1 Una pila y sus elementos.
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);
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.
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
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.
push(s,symb);
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
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 ;
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"
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
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 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 *!
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 *+
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;
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).
n! = 1 if n == o
n! = n * <n - 1 ) * (
n - 2l * * 1 if n > o
O! 1
1! 1
2! 2
3!
* 3
1
4!
* 2
* 1
4
... .... *
3 * 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.
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
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 *!
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
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,
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
..
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
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
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
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
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
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.
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;
: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
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.
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);
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
(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
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
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.
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
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):
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)
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
(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.
+-1
L __ - ~~jS~;~
5 9 nulo next(p) = s;
1 s = p;
(e)
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
+-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.
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.
X2 X2 X2
X3
~
X4 X3 X3
X5 X4 X4
.
X6 xs X5
X6 X6
q - getnode();
info( q) x;
next(g) = next(p);
next(p) = g;
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
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 "' - .=
~
~
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 "'
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
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
,
,
*p, *q);
P--- otJEJ (d) Figura 4.3.2
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
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
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.
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-
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;
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.
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.
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.
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.).
. (b)
Fiflra 5.1.2 Estructllras que no
son rboles binarios.
e) Figura 5.1. 3 Un rbol estrictamente binario.
-- - ---~----~-
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.
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
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
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.
ABC - DE F * * +
$ (Figura 5. l. 9c)
$
ABC * + AB +C * $ (Figura 5. l. 9d)
(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.
(a)
e
o 2 3 4 5 6 7 8 9 10 11 12
i
'I
o 2 3 4 5 6. 7 8 9
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;
{
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; ,;,
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.
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
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.
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.
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
=> . 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.)
=> 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):
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
Representaciones de rboles en C
#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
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
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.
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
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.
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.
nulo oprtr
nulo opnd 4
(a)
Preorden: + * AB + * CDt:
En orden: A * 8 + C * D + H hijo prximo etiqueta operador/val
Postorden: AB * CD * ,~- + +
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;
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:
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
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
+ +
x=w=tt
+
** _;
X
-3
.
X X
_,
X
-3
X
-4 -.1
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
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
.
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
;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.
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 !
,' !
,
/(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
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 */
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
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:
Heapsort
(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
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;
0
(a) Arbol origiral. (b) x[7] = pqmaxdelete (x, 8)
j/
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])
F(I) - - - ~ 12 null
I*
i = o;
copiar los ltimos nmeros en el arreglo x *I
~
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
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
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
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.
mtodos es un gnfo avnce en el camino a recorrer para llegar a ser un buen progra-
mador.
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-
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
, 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:
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.
dario. El ndice secundario acta como un ndice al ndice primario que apunta a las
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- .
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
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);
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
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
(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)
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.
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;
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
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);
(e)
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
__,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.
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)) {
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.
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
(a)
50 100 150
B ;
A B
135 142
75 D
CI C2
(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.7
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
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-
.
.
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
t t
A H e A B e D E F
(a) Eliminacin de 65, consolidacin y prstamo. (b) Eliminacin de 173 y una consolidacin doble .
....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.
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,
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
00
n
35 90
"
00
e'
o
,5,
"
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- ~
"' "
~
00 M ;; ,a
para una utilizacin de memoria del 90.9 por ciento. Con ms llaves, la utilizacin M M
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
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
"'a
Arboles-e+
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.