You are on page 1of 219

ESTRUCTURAS DE DATOS FUNDAMENTALES

1-

TDA (Tipos abstractos de datos)

En clases anteriores hemos estudiado estructuras de datos como: arreglos,


punteros, estructuras, funciones, clases, en esta unidad estudiaremos las
estructuras de datos como pilas, colas, listas, rboles, grafos, conjuntos y
tablas hash.
Una vez que se ha elegido el algoritmo, la implementacin puede
hacerse usando las estructuras mas simples, comunes en casi todos los
lenguajes de programacin: escalares, arreglos y matrices. Sin embargo
algunos problemas se pueden plantear en forma mas simple o eficiente
en trminos de estructuras informticas mas complejas, como listas,
pilas, colas, arboles, grafos, conjuntos.
El uso de estas estructuras tiene una serie de ventajas:
Se ahorra tiempo de programacin ya que no es necesario codificar.
Estas implementaciones suelen ser eficientes y robustas.
Separan dos capas de cdigo bien diferentes, por una parte el algoritmo que
escribe el programador, y por otro las rutinas de acceso a las diferentes
estructuras.
Existen estimaciones bastante uniformes de los tiempos de ejecucin de las
diferentes operaciones.
Las

funciones

asociadas

cada

estructura

son

relativamente

independientes del lenguaje o la implementacin en particular. As, una


vez que se plantea un algoritmo en trminos de operaciones sobre una
tal estructura es fcil implementarlo en una variedad de lenguajes con una
performance similar.
Un Tipo Abstracto de Datos (TAD) es un Modelo matemtico con un conjunto
de valores y un conjunto de operaciones. Cuando usamos una estructura
compleja como un conjunto, lista o pila podemos separar tres pasos para la
solucin de un determinado problema:

Modelo Matemtico
Algoritmo Informal

ADT

Estruct. de Datos

Prog. Pseudocdigo

Programa

Modelo Matemtico: Formalizar el problema para encontrar un modelo para


resolverlo.
Algoritmo Informal: Describir con lenguaje natural los pasos a seguir.
ADT: Seleccionar el tipo de datos abstractos que nos permita resolver,
basndose en el modelo matemtico seleccionado, cuales son los valores y las
operaciones a utilizar.
Prog. Pseudocdigo: Hacer una descripcin mediante un pequeo programa,
Diagrama de flujo, cdigos en C, etc.
Estruct. De Datos: Seleccionar el tipo de estructura que me permita
implementar el ADT.
Programa: Desarrollar un programa en un determinado lenguaje de
programacin que funcione correctamente.
Ejemplo: Suponga que queremos definir untito de datos basado en el modelo
matemtico de las matrices de orden n con las operaciones de suma,
multiplicacin, y determinante, de modo que podamos realizar operaciones
tales como las siguientes:
Dadas las matrices A, B y C de orden n, calcular el determinante de la matriz
A+BXC.
Paso 1. Modelo Matemtico:
Suma y multiplicacin de matrices.
Paso 1.1 Algoritmo informal:
Leer las matrices A, B y C.
Multiplicar B y C y poner el resultado en la matriz M1
Sumar A y M1 y poner el resultado en la matriz M2
Calcular el determinante de la matriz M2 y poner el resultado en R
Imprimir R.

Paso 2. ADT:
Tipo de dato abstracto MATRIZ y sus valores sern
Sea M1, M2, M3 de tipo matriz y R de tipo real
Las operaciones son: multiplicar (M1, M2, M3)
Sumar (M1, M2, M3)
Determinante M1
Leer Matriz M1
Paso 2.1 Pseudo cdigo o Algoritmo:
Inicio
Leer matriz (A)
Leer matriz (B)
Leer matriz (C)
Multiplicar (B,C y M1)
Sumar (A, M1, M2)
Poner en R el determinante (M2)
Imprimir R
Fin
Paso 3. Estructura de Datos:
Arreglo de 2 dimensiones.
Paso 3.1 Implementacin de programa
Unit matriz
Interfase
Const n = 20; {orden de la matriz}
0 = 5e-13;
Type
Matriz = array [1..n,1n] of Real;
Procedure multiplicar (M1, M2: matriz; VAR M3: matriz);
Procedure sumar (M1, M2: matriz; VAR M3: matriz);
Function determinante (M1: matriz) of Real;
Procedure leer matriz (VAR M1:matriz);

Implementation:
Var
I, j, k: Integer;
procedure multiplicar;
begin
for j: =1 to n do
M3 [I, j]:=0;
for k: =1 to n do begin
M3 [I, j]:=M3 [I, j] +M1 [I, j]*M2 [k, j]
end
end; {multiplicar}
procedure sumar;
begin
for i: =1 to n do
for j: =1 to n do
M3 [I, j]:=M1 [I, j] +M2 [I, j]
end; {sumar}
procedure leer matriz;
begin
end; {leer matriz}
end
PROGRAM Ejemplo;
USES
Matriz;
VAR
A,B,C,M1;M2,matriz
R: Real;
Begin
Leer matriz(A);
Leer matriz(B);
Leer matriz(C);
Multiplicar(B,C,M1);
Sumar(A,M1,M2);
4

R:=determinante(M2);
writeln(El resultado es: ;R)
End
Ejercicio: Aplicar los pasos para la solucin de un problema utilizando la PC
que permita resolver una ecuacin de segundo grado.

Tiempo de ejecucin de un programa


Mas importante que saber escribir programas eficientemente es saber cuando y
donde preocuparse por la eficiencia. Antes que nada, un programa est
compuesto en general por varios componentes o mdulos. No tiene sentido
preocuparse por la eficiencia de un dado modulo si este representa un 5 % del
tiempo total de calculo. En un tal modulo tal vez sea mejor preocuparse por la
robustez y sencillez de programacin que por la eficiencia.
El tiempo de ejecucin de un programa (para fijar ideas, pensemos por
ejemplo en un programa que ordena de menor a mayor una serie de
nmeros enteros) depende de una variedad de factores, entre los cuales
La eficiencia del compilador y las opciones de optimizacin que pasamos al
mismo en tiempo de compilacin.
El tipo de instrucciones y la velocidad del procesador donde se ejecutan las
instrucciones compiladas.
Los datos del programa. En el ejemplo, la cantidad de nmeros y su
distribucin estadstica:
Son todos iguales?, estn ya ordenados o casi ordenados?

Rutina simple para buscar un elemento l en un arreglo a[] de


longitud n.
En muchos casos, el tiempo de ejecucin depende no tanto del conjunto de
datos especficos, sino de alguna medida del tamao de los datos. Por ejemplo
sumar un arreglo de n nmeros no depende de los nmeros en si mismos
sino de la longitud n del arreglo. Denotando por T (n) el tiempo de

ejecucin T (n) = cn

donde c es una constante que representa el tiempo

necesario para sumar un elemento. En otros muchos casos, si bien el tiempo


de ejecucin si depende de los datos especficos, en promedio solo
depende del tamao de los datos. Por ejemplo, si buscamos la ubicacin
de un elemento l en un arreglo a, simplemente recorriendo el arreglo
desde el comienzo hasta el fin hasta encontrar el elemento, entonces el
tiempo de ejecucin dependera fuertemente de la ubicacin del elemento
dentro del arreglo. El tiempo de ejecucin es proporcional a la posicin j
del elemento dentro del vector tal que aj

= l, tomando j = n si el

elemento no est. El mejor caso es cuando el elemento est al principio


del arreglo, mientras que el peor es cuando est al final o cuando no est. Pero
en promedio (asumiendo que la distribucin de elementos es aleatoria) el
elemento buscado estarla en la zona media del arreglo, de manera que una
ecuacin como la (1.16) ser vlida (en promedio). Cuando sea necesario
llamaremos Tprom(n) al promedio de los tiempos de ejecucin de un dado
algoritmo sobre un ensamble de posibles entradas y por Tpeor (n) el peor de
todos sobre el ensamble. Entonces, para el caso de buscar la numeracin de un
arreglo tenemos:
T (n) = cj
Tpeor (n) = cn n Tprom(n) = c 2 En estas expresiones c puede tomarse como
el tiempo necesario para ejecutar una vez el cuerpo del lazo en la rutina
search (). Notar que esta constante c, si la medimos en segundos,
puede depender fuertemente de los tems considerados en los dos primeros
puntos de la lista anterior. Por eso, preferimos dejar la constante sin especificar
en forma absoluta, es decir que de alguna forma estamos evaluando el tiempo
de ejecucin en trminos de unidades de trabajo, donde una unidad de
trabajo c es el tiempo necesario para ejecutar una vez el lazo.
En general, determinar analticamente el tiempo de ejecucin de un algoritmo
puede ser una tarea intelectual ardua. Muchas veces, encontrar el Tpeor (n) es
una tarea relativamente ms fcil. Determinar el Tprom(n) puede a veces ser
ms fcil y otras veces ms difcil.

2-

RECURSIVIDAD

Se dice que algo es recursivo si se define en funcin de s mismo o a s


mismo. Tambin se dice que nunca se debe incluir la misma palabra en la
definicin de sta. El caso es que las definiciones recursivas aparecen con
frecuencia en matemticas, e incluso en la vida real. Un ejemplo: basta con
apuntar una cmara al monitor que muestra la imagen que muestra esa
cmara. El efecto es verdaderamente curioso, en especial cuando se mueve la
cmara alrededor del monitor.
En matemticas, tenemos mltiples definiciones recursivas:
- Nmeros naturales:
(1)
(2)

1 es nmero natural.
el siguiente nmero de un nmero natural es un nmero natural

- El factorial: n!, de un nmero natural (incluido el 0):


(1) Si n = 0 entonces: 0! = 1
(2) si n > 0 entonces: n! = n (n-1)!
Asimismo, puede definirse un programa en trminos recursivos, como una serie
de pasos bsicos, o paso base (tambin conocido como condicin de parada),
y un paso recursivo, donde vuelve a llamarse al programa. En un computador,
esta serie de pasos recursivos debe ser finita, terminando con un paso base.
Es decir, a cada paso recursivo se reduce el nmero de pasos que hay que dar
para terminar, llegando un momento en el que no se verifica la condicin de
paso a la recursividad. Ni el paso base ni el paso recursivo son necesariamente
nicos.
Por otra parte, la recursividad tambin puede ser indirecta, si tenemos un
procedimiento P que llama a otro Q y ste a su vez llama a P. Tambin en estos
casos debe haber una condicin de parada.
Existen ciertas estructuras cuya definicin es recursiva, tales como los rboles,
y los algoritmos que utilizan rboles suelen ser en general recursivos.

Un ejemplo de programa recursivo en C, el factorial:


int factorial (int n);
{
if (n == 0)
return 1;
else
return n * factorial(n-1);
}
Como se observa, en cada llamada recursiva se reduce el valor de n, llegando
el caso en el que n es 0 y no efecta ms llamadas recursivas. Hay que
apuntar que el factorial puede obtenerse con facilidad sin necesidad de
emplear funciones recursivas, es ms, el uso del programa anterior es muy
ineficiente, pero es un ejemplo muy claro.
A continuacin se expone un ejemplo de programa que utiliza recursin
indirecta, y nos dice si un nmero es par o impar. Al igual que el programa
anterior, hay otro mtodo mucho ms sencillo de determinar si un nmero es
par o impar, basta con determinar el resto de la divisin entre dos. Por ejemplo:
si hacemos par(2) devuelve 1 (cierto). Si hacemos impar(4) devuelve 0 (falso).
/* Declaracin de funciones, para evitar errores */
int par(int n);
int impar(int n);
int par(int n)
{
if (n == 0) return 1;
return impar(n-1);
}
int impar(int n)
{
if (n == 0) return 0;
return par(n-1);
}
8

En Pascal se hace as (notar el uso de forward):


function impar(n : Integer) : Boolean; forward;
function par(n : Integer) : Boolean; forward;
function par(n : Integer) : Boolean;
begin
if n = 0 then par := true
else par := impar(n-1)
end;
function impar(n : Integer) : Boolean;
begin
if n = 0 then impar := false
else impar := par(n-1)
end;
Ejemplo: si hacemos la llamada impar(3) hace las siguientes llamadas:
par(2)
impar(1)
par(0) -> devuelve 1 (cierto)
Por lo tanto 3 es un nmero impar.
Qu pasa si se hace una llamada recursiva que no termina?
Cada llamada recursiva almacena los parmetros que se pasaron al
procedimiento, y otras variables necesarias para el correcto funcionamiento del
programa. Por tanto si se produce una llamada recursiva infinita, esto es, que
no termina nunca, llega un momento en el que no quedar memoria para
almacenar ms datos, y en ese momento se abortar la ejecucin del
programa. Para probar esto se puede intentar hacer esta llamada en el
programa factorial definido anteriormente:
factorial(-1);
Por supuesto no hay que pasar parmetros a una funcin que estn fuera de
su dominio, pues el factorial est definido solamente para nmeros naturales,
pero es un ejemplo claro.

Cundo utilizar la recursin?


Para empezar, algunos lenguajes de programacin no admiten el uso de
recursividad, como por ejemplo el ensamblador o el FORTRAN. Es obvio que
en ese caso se requerir una solucin no recursiva (iterativa). Tampoco se
debe utilizar cuando la solucin iterativa sea clara a simple vista. Sin embargo,
en otros casos, obtener una solucin iterativa es mucho ms complicado que
una solucin recursiva, y es entonces cuando se puede plantear la duda de si
merece la pena transformar la solucin recursiva en otra iterativa.
Posteriormente se explicar como eliminar la recursin, y se basa en
almacenar en una pila los valores de las variables locales que haya para un
procedimiento en cada llamada recursiva. Esto reduce la claridad del programa.
An as, hay que considerar que el compilador transformar la solucin
recursiva en una iterativa, utilizando una pila, para cuando compile al cdigo
del computador.
Por otra parte, casi todos los algoritmos basados en los esquemas de vuelta
atrs y divide y vencers son recursivos, pues de alguna manera parece mucho
ms natural una solucin recursiva.
Aunque parezca mentira, es en general mucho ms sencillo escribir un
programa recursivo que su equivalente iterativo. Si el lector no se lo cree,
posiblemente se deba a que no domine todava la recursividad. Se propondrn
diversos ejemplos de programas recursivos de diversa complejidad para
acostumbrarse a la recursin.
La famosa sucesin de Fibonacci puede definirse en trminos de recurrencia
de la siguiente manera:

-3 Fib(1) = 1 ; Fib(0) = 0
(2) Fib(n) = Fib(n-1) + Fib(n-2) si n >= 2
Cuantas llamadas recursivas se producen para Fib(6)?.
Codificar un programa que calcule Fib(n) de forma iterativa.
Nota: no utilizar estructuras de datos, puesto que no queremos almacenar los
nmeros de Fibonacci anteriores a n; s se permiten variables auxiliares.

10

Ejemplos de programas recursivos

-3 Dados dos nmeros a (nmero entero) y b (nmero natural mayor o


igual que cero) determinar a^b.
int potencia(int a, int b)
{
if (b == 0)
return 1;
else
return a * potencia(a, b-1);
}
La condicin de parada se cumple cuando el exponente es cero. Por ejemplo,
la evaluacin de potencia(-2, 3) es:
potencia(-2, 3) ->
(-2) potencia(-2, 2) ->
(-2) (-2) potencia(-2, 1) ->
(-2) (-2) (-2) potencia(-2, 0) ->
(-2) (-2) (-2) 1
y a la vuelta de la recursin se tiene:
(-2) (-2) (-2) 1 /=/ (-2) (-2) (-2) potencia(-2,0)
< (-2) (-2) (-2) /=/ (-2) (-2) potencia(-2, 1)
< (-2) 4 /=/ (-2) potencia(-2,2)
< -8 /=/ potencia(-2,3)
en negrita se ha resaltado la parte de la expresin que se evala en cada
llamada recursiva.

-3 Dado un array constituido de nmeros enteros y que contiene N


elementos siendo N >= 1, devolver la suma de todos los elementos.
Int sumarray(int numeros[], int posicion, int N)
{
if (posicin == N-1)
return nmeros [posicin];
else
return numeros[posicin] + sumarray(numeros, posicin+1, N);
}

11


int numeros[5] = {2,0,-1,1,3};
int N = 5;
printf(%d\n,sumarray(numeros, 0, N));
Notar que la condicin de parada se cumple cuando se llega al final del array.
Otra alternativa es recorrer el array desde el final hasta el principio (de derecha
a izquierda):
int sumarray(int numeros[], int posicion)
{
if (posicin == 0)
return numeros[posicin];
else
return numeros[posicin] + sumarray(numeros, 12osicin-1);
}

int numeros[5] = {2,0,-1,1,3};


int N = 5;
printf(%d\n,sumarray(numeros, N-1));

-3 Dado un array constituido de nmeros enteros, devolver la suma de


todos los elementos. En este caso se desconoce el nmero de
elementos. En cualquier caso se garantiza que el ltimo elemento del
array es -1, nmero que no aparecer en ninguna otra posicin.
Int sumarray(int numeros[], int posicion)
{
if (numeros[12osicin] == -1)
return 0;
else
return numeros[Posicin] + sumarray(numeros, Posicin+1);
}

12


int numeros[5] = {2,4,1,-3,-1};
printf(%d\n,sumarray(numeros, 0));
La razn por la que se incluye este ejemplo se debe a que en general no se
conocer el nmero de elementos de la estructura de datos sobre la que se
trabaja. En ese caso se introduce un centinela como la constante 1 de este
ejemplo o la constante NULO para punteros, u otros valores como el mayor o
menor entero que la mquina pueda representar para indicar el fin de la
estructura.
- Dado un array constituido de nmeros enteros y que contiene N elementos
siendo N >= 1, devolver el elemento mayor.
Int mayor(int numeros[], int posicion)
{
int aux;
if (Posicin == 0)
return numeros[Posicin];
else {
aux = mayor(numeros, Posicin-1);
if (numeros[Posicin] > aux)
return numeros[Posicin];
else
return aux;
}
}

int numeros[5] = {2,4,1,-3,-1};


int N = 5;
printf(%d\n, mayor(numeros, 4));

13

Ahora uno un poco ms complicado: dados dos arrays de nmeros enteros A y


B de longitud n y m respectivamente, siendo n >= m, determinar si B est
contenido en A
Ejemplo:
A = {2, 3, 4, 5, 6, 7, 3}
B = {7,3} -> contenido; B = {5,7} -> no contenido; B = {3,2} -> no contenido
Para resolverlo, se parte del primer elemento de A y se compara a partir de ah
con todos los elementos de B hasta llegar al final de B o encontrar una
diferencia.
A = {2,3,4,5}, B = {3,4}
-2,3,4,5
3,4
^
En el caso de encontrar una diferencia se desplaza al segundo elemento de A y
as sucesivamente hasta demostrar que B es igual a un subarray de A o que B
tiene una longitud mayor que el subarray de A.
3,4,5
3,4
Visto de forma grfica consiste en deslizar B a lo largo de A y comprobar que
en alguna posicin B se superpone sobre A.
Se han escrito dos funciones para resolverlo, contenido y esSubarray. La
primera devuelve cierto si el subarray A y el array B son iguales; tiene dos
condiciones de parada: o que se haya recorrido B completo o que no coincidan
dos elementos. La segunda funcin es la principal, y su cometido es ir
deslizando B a lo largo de A, y en cada paso recursivo llamar una vez a la
funcin contenido; tiene dos condiciones de parada: que el array B sea mayor
que el subarray A o que B est contenido en un subarray A.
int contenido(int A[], int B[], int m, int pos, int desp)
{
if (pos == m)
return 1;
14

else if (A[desp+pos] == B[pos])


return contenido(A,B,m, pos+1, desp);
else
return 0;
}
int esSubarray(int A[], int B[], int n, int m, int desp)
{
if (desp+m > n)
return 0;
else if (contenido(A, B, m, 0, desp))
return 1;
else
return esSubarray(A, B, n, m, desp+1);
}

int A[4] = {2, 3, 4, 5};


int B[3] = {3, 4, 5};
if (esSubarray(A, B, 4, 5, 0))
printf(\Nb esta contenido en A);
else
printf(\Nb no esta contenido en A);
Hay que observar que el requisito n >= m indicando en el enunciado es
innecesario, si m > n entonces devolver falso nada ms entrar en la ejecucin
de esSubarray.
Este algoritmo permite hacer bsquedas de palabras en textos. Sin embargo
existen algoritmos mejores no me refiero a una implementacin iterativa en
lugar de esta recursiva- como el de Knuth-Morris-Prat, el de Rabin-Karp o
mediante autmatas finitos; estos algoritmos som ms complicados pero
mucho ms efectivos.
Dado un array constituido de nmeros enteros y que contiene N elementos
siendo N >= 1, devolver el elemento mayor. En este caso escribir un
procedimiento, es decir, que el elemento mayor devuelto sea una variable que
se pasa por referencia.
15

Void mayor(int numeros[], int posicion, int *m)


{
if (16osicin == 0)
*m = numeros[16osicin];
else {
mayor(numeros, 16osicin-1, m);
if (numeros[16osicin] > *m)
*m = numeros[16osicin];
}
}
int numeros[5] = {2,4,1,-3,-1};
int M;
mayor(numeros, 5-1, &M);
printf(%d\n, M);
Hemos incluido este ejemplo porque se suelen cometer dos errores: el primero
es declarar la variable para que se pase por valor y no por referencia, con lo
cual no se obtiene nada. El otro error consiste en llamar a la funcin pasando
en lugar del parmetro por referencia una constante, por ejemplo: mayor
(numeros, 5, 1, 0); en este caso adems se producir un error de compilacin.
EJERCICIOS PROPUESTOS:
1.

Hacer un programa que sume, reste, divida y multiplique dos valores de


manera recursiva.

2.

Desarrollar con todos los conocimientos adquiridos un programa en C++


que funcione como una calculadora utilizando un men de opciones y las
funciones recursivas (suma, resta, divisin, multiplicacin) desarrolladas en el
ejercicio anterior.

16

3-

El TAD LISTA

Las listas constituyen una de las estructuras lineales ms flexibles, porque


pueden crecer y acortarse segn se requiera, insertando o suprimiendo
elementos tanto en los extremos como en cualquier otra posicin de la lista. Por
supuesto

esto

tambin

puede

hacerse

con

vectores,

pero

en

las

implementaciones mas comunes estas operaciones son O(n) para los vectores,
mientras que son O(1) para las listas. El poder de las listas es tal que la familia
de lenguajes derivados del Lisp, que hoy en da cuenta con el Lisp, Common
Lisp y Scheme, entre otros, el lenguaje mismo est basado en la lista (Lisp
viene de list processing).
Descripcin matemtica de las listas Desde el punto de vista abstracto, una lista
es una secuencia de cero o ms elementos de un tipo determinado, que en
general llamaremos elem_t, por ejemplo int o double. A menudo representamos
una lista en forma impresa como una sucesin de elementos entre parntesis,
separados

por

comas

(a0

a1,

an1

donde n 0 es el numero de elementos de la lista y cada ai es de tipo


elem_t. Si n 1 entonces decimos que a0 es el primer elemento y an1 es
el ltimo elemento de la lista. Si n = 0 decimos que la lista est vaca. Se dice
que n es la longitud de la lista.
Una propiedad importante de la lista es que sus elementos estn
ordenados en forma lineal, es decir, para cada elemento ai existe un sucesor
ai+1 (si i < n 1) y un predecesor ai1 (si i > 0). Este orden es parte de la lista,
es decir, dos listas son iguales si tienen los mismos elementos y en el mismo
orden. Por ejemplo, las siguientes listas son distintas (1, 3, 7, 4) = (3, 7, 4, 1)
Mientras

que

en

el

caso

de

conjuntos, estos

seran

iguales.

Otra

diferencia con los conjuntos es que puede haber elementos repetidos en una
lista, mientras que en un conjunto no.

17

Decimos

que

el

elemento

ai

est

en

la

posicin

i.

Tambin

introducimos la nocin de una posicion ficticia n que est fuera de la


lista. A las posiciones en el rango 0 i n 1 las llamamos de
referenciales ya que pertenecen a un objeto real, y por lo tanto podemos
obtener una referencia a ese objeto. Notar que, a medida que se vayan
insertando o eliminando elementos de la lista la posicin ficticia n va variando,
de manera que convendra tener un mtodo de la clase end() que retorne esta
posicin.
Operaciones abstractas sobre listas Consideremos un operacin tpica sobre
las listas que consiste en eliminar todos los elementos duplicados de la
misma. El algoritmo ms simple consiste en un doble lazo, en el cual el lazo
externo sobre i va desde el comienzo hasta el ltimo elemento de la lista. Para
cada elemento i el lazo interno recorre desde i + 1 hasta el ltimo elemento,
eliminado los elementos iguales a i. Notar que no hace falta revisar los
elementos anteriores a i (es decir, los elementos j con j < i), ya que, por
construccin, todos los elementos de 0 hasta i son distintos. Este problema
sugiere las siguientes operaciones abstractas
Dada una posicin i, insertar el elemento x en esa posicin, por ejemplo
L = (1, 3, 7)
inserta 5 en la posicin 2
L = (1, 3, 5, 7) (2.3)
Notar que el elemento 7, que estaba en la posicin 2, se desplaza hacia el
fondo, y termina en la posicin 3. Notar que es valido insertar en cualquier
posicin dereferenciable, o en la posicin ficticia end()
Dada una posicin i, suprimir el elemento que se encuentra en la misma. Por
ejemplo,
L = (1, 3, 5, 7)
Suprime elemento en la posicin 2 L = (1, 3, 7) (2.4)
Notar que esta operacin es, en cierta forma, la inversa de insertar. Si, como en
18

el ejemplo anterior, insertamos un elemento en la posicin i y despus


suprimimos en esa misma posicin, entonces la lista queda inalterada. Notar
que slo es vlido suprimir en las posiciones de referenciales.
Si representramos las posiciones como enteros, entonces avanzar la posicin
podra efectuarse con la sencilla operacin de enteros i i + 1, pero es
deseable

pensar

en

las

posiciones

como

entidades abstractas, no

necesariamente enteros y por lo tanto para las cuales no necesariamente es


vlido hacer operaciones de enteros. Esto lo sugiere la experiencia previa de
cursos bsicos de programacin donde se ha visto que las listas se representan
por celdas encadenadas por punteros. En estos casos, puede ser deseable
representar a las posiciones como punteros a las celdas. De manera que
asumiremos que las posiciones son objetos abstractos. Consideramos entonces
las operaciones abstractas:
Acceder al elemento en la posicin p, tanto para modificar el valor (ap x)
como para acceder al valor (x ap).
Avanzar una posicin, es decir dada una posicin p correspondiente al elemento
ai, retornar la posicin q correspondiente al elemento ai+1 . (Como
mencionamos previamente, no es necesaria- mente q = p + 1, o ms atn,
pueden no estar definidas estas operaciones aritmticas sobre las posiciones p
y q.)
Retornar la primera posicin de la lista, es decir la correspondiente al elemento
a0.
Retornar la posicin ficticia al final de la lista, es decir la correspondiente a n.
PRINCIPALES OPERACIONES DE LISTAS
INSERT: X, P, L
Inserta el nodo X en la posicin P de la lista moviendo los elementos a partir
de dicha posicin a la prxima.

19

LOCATE: X, L
Da la posicin de x en la lista , es decir localiza un determinado valor o
elemento en la lista si x aparece mas de una vez dar la posicin de la primera
ocurrencia de x en la lista si x no aparece el resultado coincide con fin L.
RETRIEVE: P, L
Da el elemento que se encuentra en la posicin P de la lista L. Si P= Fin L o no
existe la posicin P en la lista L el resultado s indefinido.
DELETE: P,L
Extrae el elemento que se encuentra n la posicin P de la lista L es decir si L es
la lista el resultado ser indefinido cuando P =fin L o no existe la posicin Pen
la lista L. Borra la informacin que se encuentra n la posicin P de la listaL.
NEXT P,L
Da la posicin que sucede ala posicin P en la lista L. Si P es la posicin del
ultimo elemento de la lista L entonces Next P,L retornara el valor Fin L mientras
que queda indefinida si P es Fin L o la lista L no tiene la posicin P es decir
regresa la posicion P+1.
PREVIUS L
Regresa la posicin anterior a la posicin P. Da la posicin que precede a la
posicin P en la lista L. Si P es la posicin del primer elemento de la lista L
entonces Previus P,L queda indefinido as como si la posicin P no existe en la
L.
MAKENULL L
Hace que L se convierta en una lista vaca para inicializar se utiliza una variable
de tipo Lista
FIRST L
Da la posicin del primer elemento de la lista L si L es una lista vaca devuelve
la posicin Fin L

20

SWAP P,Q,L
Intercambian los elementos que se encuentran en la posicin P y Q de la lista L
si alguna de estas posiciones no existe o es Fin L su accin quedara indefinida.
LENGTH L
Da la cantidad elementos que tiene la lista L
EMPTY L
Da TRUE si y solo si Les una lista vaca.
Algunas observaciones con respecto a estas funciones son
. Posiciones invlidas: Un elemento a tener en cuenta es que las funciones
que modifican la lista como insert and erase, convierten en invlidas
algunas de las posiciones de la lista, normalmente desde el punto de
insercin/supresin en adelante, incluyendo end(). Por ejemplo,
1 iterator_t p,q,r;
2 list L;
3 elem_t x,y,z;
4 //...
5 // p es una posicion dereferenciable
6 q = L.next(p);
7 r = L.end();
8 L.erase(p);
9 x = *p;

// incorrecto

10 y = *q;

// incorrecto

11 L.insert(r,z); // incorrecto
Ya que p,q,r ya no son validos (estn despus de la posicin borrada p. La
forma correcta de escribir el cdigo anterior es
1 iterator_t p,q,r;
2 list L;
3 elem_t x,y,z;

21

4 //...
5 // p es una posicion dereferenciable
6 p = L.erase(p);
7 x = *p;

// correcto

8 q = L.next(p);
9 y = *q;

// correcto

10 r = L.end();
11 L.insert(r,z); // correcto
. Las posiciones slo se acceden a travs de funciones de la clase: Las nicas
operaciones vlidas con posiciones son
Asignar:
p = L.begin();
q = L.end();
Avanzar:
q = L.next(p);
Acceder al elemento:
x

L.retrieve(p);

L.retrieve(q) = y;
Copiar:
q = p;
Comparar: Notar que slo se puede comparar por igualdad o desigualdad, no
por opera- dores de comparacin, como < o >.
q == p
r != L.end();

LAS LISTAS PUEDEN SER REPRESENTADAS MEDIANTE:


ARREGLOS
En una implementacin mediante arreglos

los nodos de la lista se

almacenaran en localizaciones contiguas es decir secuencialmente.

22

Desventaja.
Esta forma de almacenamiento tiene la desventaja de que al insertar o extraer
un nodo en una posicin intermedia e la lista es necesario hacer corrimiento
fsico de informacin ya sea para hacer espacio cuando se va a insertar un
nuevo elemento o compactar la lista si se extrajo un elemento.
Ventaja
Estas operaciones e hacen muy eficientes cuando e trata de la ultima posicin
o del ultimo nodo de la lista respectivamente.
IMPLEMENTACION MEDIANTE PUNTEROS.
En esta implementacin consideremos que cada nodo de la lista contiene un
campo adicional que es de tipo puntero el cual se utilizara para enlazar con el
prximo nodo obtenindose una lista simplemente enlazada. Esto nos evitara
tener que almacenar todos los nodos de la lista en localizaciones contiguas o
hacer corrimiento fsico cuando se va a extraer o insertar en posiciones
intermedios de la lista.
Esta implementacin mediante punteros tiene la desventaja que consume
memoria adicional de dichos punteros.

23

4-

LISTAS DOBLEMENTE ENLAZADAS.

En algunas aplicaciones podemos desear recorrer la lista hacia adelante y


hacia atrs, o dado un elemento, podemos desear conocer rpidamente los
elementos anterior y siguiente. En tales situaciones podramos desear darle a
cada celda sobre una lista un puntero a las celdas siguiente y anterior en la
lista tal y como se muestra en la figura.

Otra ventaja de las listas doblemente enlazadas es que podemos usar un


puntero a la celda que contiene el i-simo elemento de una lista para
representar la posicin i, mejor que usar el puntero a la celda anterior aunque
lgicamente, tambin es posible la implementacin similar a la expuesta en las
listas simples haciendo uso de la cabecera. El nico precio que pagamos por
estas caractersticas es la presencia de un puntero adicional en cada celda y
consecuentemente procedimientos algo ms largos para algunas de las
operaciones bsicas de listas. Si usamos punteros (mejor que cursores)
podemos declarar celdas que consisten en un elemento y dos punteros a
travs de:
typedef struct celda{
tipoelemento elemento;
struct celda *siguiente,*anterior;
}tipocelda;
typedef tipocelda *posicion;

Un procedimiento para borrar un elemento en la posicin p en una lista


doblemente enlazada es:
void borrar (posicion p)
{
if (p->anterior != NULL)
24

p->anterior->siguiente = p->siguiente;
if (p->siguiente != NULL)
p->siguiente->anterior = p->anterior;
free(p);
}

El procedimiento anterior se expresa de forma grfica en como muestra la


figura:

Donde los trazos contnuos denotan la situacin inicial y los punteados la final.
El ejemplo visto se ajusta a la supresin de un elemento o celda de una lista
situada en medio de la misma. Para obviar los problemas derivados de los
elementos extremos (primero y ltimo) es prctica comn hacer que la
cabecera de la lista doblemente enlazada sea una celda que efectivamente
complete el crculo, es decir, el anterior a la celda de cabecera sea la ltima
celda de la lista y la siguiente la primera. De esta manera no necesitamos
chequear para NULL en el anterior procedimiento borrar.
Por consiguiente, podemos realizar una implementacin de listas doblemente
enlazadas con cabecera tal que tenga una estructura circular en el sentido de
que dado un nodo y por medio de los punteros siguientes podemos volver
hasta l como se puede observar en la figura (de forma anloga para anterior).

25

Es importante notar que aunque la estructura fsica de la lista puede hacer


pensar que mediante la operacin siguiente podemos alcanzar de nuevo un
nodo de la lista, la estructura lgica es la de una lista y por lo tanto habr una
posicin primero y una posicin fin de forma que al aplicar una operacin
anterior o siguiente respectivamente sobre estas posiciones el resultado ser
un error.
Respecto a la forma en que trabajarn las funciones de la implementacin que
proponemos hay que hacer constar los siguientes puntos:

La funcin de creacin debe alojar memoria para la cabecera y hacer que


los punteros siguiente y anterior apunten a ella, devolviendo un puntero a dicha
cabecera.

La funcin primero (l) devolver un puntero al nodo siguiente a la cabecera.

La funcin fin (l) devolver un puntero al nodo cabecera.

Trabajar con varias posiciones simultneamente tendr un comportamiento


idntico al de las listas enlazadas excepto respecto al problema referente al
borrado cuando se utilizan posiciones consecutivas. Es posible implementar la
funcin de borrado de tal forma que borrar un elemento de una posicin p
invalida el valor de dicha posicin p y no afecta a ninguna otra posicin.
Nosotros en nuestra implementacin final optaremos por pasar un puntero a la
posicin para el borrado de forma que la posicin usada quede apuntando al
elemento siguiente que se va a borrar al igual que ocurra en el caso de las
listas simples. Otra posible solucin puede ser que la funcin devuelva la
posicin del elemento siguiente a ser borrado.

26

La insercin se debe hacer a la izquierda del nodo apuntado por la posicin


ofrecida a la funcin insertar. Esto implica que al contrario que en las listas
simples, al insertar un nodo, el puntero utilizado sigue apuntando al mismo
elemento al que apuntaba y no al nuevo elemento insertado. Si se desea, es
posible modificar la funcin de forma que se pase un puntero a la posicin de
insercin para poder modificarla y hacer que apunte al nuevo elemento
insertado. En cualquier caso, el comportamiento final de la funcin deber
quedar reflejado en el conjunto de especificaciones del TDA.

5-

GRAFOS

El origen de la palabra grafo es griego y su significado etimolgico es "trazar".


Aparece con gran frecuencia como respuesta a problemas de la vida cotidiana,
algunos ejemplos podran ser los siguientes: un grfico de una serie de tareas
a realizar indicando su secuenciacin (un organigrama),grafos matemticos
que representan las relaciones binarias, una red de carreteras, la red de
enlaces ferroviarios o areos o la red elctrica de una ciudad.(Vase la figura
1).En cada caso, es conveniente representar grficamente el problema
dibujando un grafo como un conjunto de puntos(vrtices)con lneas
conectndolos (arcos).

27

De aqu se podra deducir que un grafo es bsicamente un objeto geomtrico


aunque en realidad sea un objeto combinatorio, es decir, un conjunto de puntos
y un conjunto de lneas tomado de entre el conjunto de lneas que une cada par
de vrtices. Por otro lado, y debido a su generalidad y a la gran diversidad de
formas que pueden usarse, resulta complejo tratar con todas las ideas
relacionadas con un grafo.
Para facilitar el estudio de este tipo de dato, a continuacin se realizar un
estudio de la teora de grafos desde el punto de vista de las ciencias de la
computacin. Considerando que dicha teora es compleja y amplia, aqu slo
se realizar una introduccin a la misma, describindose el grafo como un tipo
de dato y mostrndose los problemas tpicos y los algoritmos que permiten
solucionarlos usando un ordenador.
Los grafos son estructuras de datos no lineales que tienen una naturaleza
generalmente dinmica. Su estudio podra dividirse en dos grandes bloques:
Grafos Dirigidos.
Grafos no Dirigidos (pueden ser considerados un caso particular de los
anteriores).
Un ejemplo de grafo dirigido lo constituye la red de aguas de una ciudad ya que
cada tubera slo admite que el agua la recorra en un nico sentido. Por el
contrario, la red de carreteras de un pas representa en general un grafo no
dirigido, puesto que una misma carretera puede ser recorrida en ambos
sentidos. No obstante, podemos dar unas definiciones generales para ambos
tipos.
A continuacin daremos definiciones de los dos tipos de grafos y de los
conceptos que llevan asociados.

DEFINICIONES Y TERMINOLOGA FUNDAMENTAL.


Un grafo G es un conjunto en el que hay definida una relacin binaria, es decir,
G=(V,A) tal que V es un conjunto de objetos a los que denominaremos vrtices
o nodos y

es una relacin binaria a cuyos elementos denominaremos

arcos o aristas.

28

Dados
1.

, puede ocurrir que:


, en cuyo caso diremos que x e y estn unidos mediante un arco,

y
2.

, en cuyo caso diremos que no lo estn.


Si las aristas tienen asociada una direccin (las aristas (x,y) y (y,x) no son
equivalentes) diremos que el grafo es dirigido, en otro caso ((x,y)=(y,x)) diremos
que el grafo es no dirigido.

Conceptos asociados a grafos:


Diremos que un grafo es completo si A=VxV, o sea, si para cualquier pareja de
vrtices existe una arista que los une (en ambos sentidos si el grafo es no
dirigido).El nmero de aristas ser:
grafos dirigidos:

grafos no dirigidos:

donde n=|V|

29

Un grafo dirigido es simtrico si para toda arista (x,y)perteneciente a tambin


aparece la arista (y,x)perteneciente a A; y es antisimtrico si dada una arista
(x,y) perteneciente a A implica que (y,x) no pertenece a A.
Tanto a las aristas como a los vrtices les puede ser asociada informacin. A
esta informacin se le llama etiqueta. Si la etiqueta que se asocia es un nmero
se le llama peso, costo o longitud. Un grafo cuyas aristas o vrtices tienen
pesos asociados recibe el nombre de grafo etiquetado o ponderado.
El nmero de elementos de V se denomina orden del grafo. Un grafo nulo es
un grafo de orden cero.
Se dice que un vrtice x es incidente a un vrtice y si existe un arco que vaya
de x a y ((x,y)pertenece a A),a x se le denomina origen del arco y a y extremo
del mismo. De igual forma se dir que y es adyacente a x. En el caso de que el
grafo sea no dirigido si x es adyacente(resp. incidente) a y entonces y tambin
es adyacente (resp. incidente) a x.
Se dice que dos arcos son adyacentes cuando tienen un vrtice comn que es
a la vez origen de uno y extremo del otro.
Se denomina camino (algunos autores lo llaman cadena si se trata de un grafo
no dirigido)en un grafo dirigido a una sucesin de arcos adyacentes:
C={(v1,v2),(v2,v3),...,(vn-1,vn), para todo vi perteneciente a V}
La longitud del camino es el nmero de arcos que comprende y en el caso en
el que el grafo sea ponderado se calcular como la suma de los pesos de las
aristas que lo constituyen.
Ejemplo.
En el grafo dirigido de la figura 2, un camino que une los vrtices 1 y 4 es C=
{(1,3),(3,2),(2,1)}, su longitud es 3.
En el grafo no dirigido de la figura 2, un camino que une los vrtices 1 y 4 es
C'= {(1,2),(2,4)}.Su longitud es 2.

30

Un camino se dice simple cuando todos sus arcos son distintos y se dice
elemental cuando no utiliza un mismo vrtice dos veces. Por tanto todo camino
elemental es simple y el recproco no es cierto.
Un camino se dice Euleriano si es simple y adems contiene a todos los arcos
del grafo.
Un circuito(o ciclo para grafos no dirigidos) es un camino en el que coinciden
los vrtices inicial y final. Un circuito se dice simple cuando todos los arcos que
lo forman son distintos y se dice elemental cuando todos los vrtices por los
que pasa son distintos. La longitud de un circuito es el nmero de arcos que lo
componen. Un bucle es un circuito de longitud 1(estn permitidos los arcos de
la forma (i,i) y notemos que un grafo antisimtrico carecera de ellos).
Un circuito elemental que incluye a todos los vrtices de un grafo lo llamaremos
circuito Hamiltoniano.
Un grafo se denomina simple si no tiene bucles y no existe ms que un camino
para unir dos nodos.
Diremos que un grafo no dirigido es bipartido si el conjunto de sus vrtices
puede ser dividido en dos subconjuntos(disjuntos) de tal forma que cualquiera
de las aristas que componen el grafo tiene cada uno de sus extremos en un
subconjunto distinto. Un grafo no dirigido ser bipartido si y slo si no contiene
ciclos con un nmero de aristas par.
Dado un grafo G=(V,A),diremos que G'=(V,A') con

es un grafo parcial de

G y un subgrafo de G es todo grafo G'=(V',A') con

donde A' ser

el conjunto de todas aquellas aristas que unan en el grafo G dos vrtices que
estn en V'. Se podran combinar ambas definiciones dando lugar a lo que
llamaremos subgrafo parcial

31

Se denomina grado de entrada de un vrtice x al nmero de arcos incidentes


en l. Se denota

Se denomina grado de salida de un vrtice x al nmero de arcos adyacentes a


l. Se denota

Para grafos no dirigidos tanto el grado de entrada como el de salida coinciden y


hablamos entonces de grado y lo notamos por

A todo grafo no dirigido se puede asociar un grafo denominado dual construido


de la siguiente forma:

donde A' est construido de la siguiente forma: si e 1,e2 pertenece a A son


adyacentes --> (e1,e2) pertenece a A' con e 1,e2 pertenece a V'.En definitiva,
para construir un grafo dual se cambian vrtices por aristas y viceversa.

32

Dado un grafo G, diremos que dos vrtices estn conectados si entre ambos
existe un camino que los une.
Llamaremos componente conexa a un conjunto de vrtices de un grafo tal que
entre cada par de vrtices hay al menos un camino y si se aade algn otro
vrtice esta concicin deja de verificarse. Matemticamente se puede ver como
que la conexin es una relacin de equivalencia que descompone a V en
clases de equivalencia, cada uno de los subgrafos a los que da lugar cada una
de esas clases de equivalencia constituira una componente conexa. Un grafo
diremos que es conexo si slo existe una componente conexa que coincide con
todo el grafo.

REPRESENTACIONES PARA EL TDA GRAFO.


Existen diversas representaciones de naturaleza muy diferente que resultan
adecuadas para manejar un grafo, y en la mayora de los casos no se puede
decir que una sea mejor que otra siempre ya que cada una puede resultar ms
adecuada dependiendo del problema concreto al que se desea aplicar. As, si
existe una representacin que es peor que otra para todas las operaciones
excepto una es posible que an as nos decantemos por la primera porque
precisamente esa operacin es la nica en la que tenemos especial inters en
que se realice de forma eficiente. A continuacin veremos dos de las
representaciones ms usuales: Matriz de adyacencia(o booleana) y Lista de
adyacencia.

33

6- MATRIZ DE ADYACENCIA.

Grafos dirigidos.
G=(V,A) un grafo dirigido con |V|=n .Se define la matriz de adyacencia o
booleana asociada a G como Bnxn con

Como se ve, se asocia cada fila y cada columna a un vrtice y los elementos b i,j
de la matriz son 1 si existe el arco (i,j) y 0 en caso contrario.
Grafos no dirigidos.
G=(V,A) un grafo no dirigido con |V|=n .Se define la matriz de adyacencia o
booleana asociada a G como Bnxn con:

La matriz B es simetrica con 1 en las posiciones ij y ji si existe la arista (i,j).

34

EJEMPLO:

Si el grafo es etiquetado, entonces tanto b i,j como bi,j representan al coste o


valor asociado al arco (i,j) y se suelen denominar matrices de coste. Si el arco
(i,j) no pertenece a A entonces se asigna b i,j o bi,j un valor que no puede ser
utilizado como una etiqueta valida.

35

La principal ventaja de la matriz de adyacencia es que el orden de eficiencia de


las operaciones de obtencin de etiqueta de un arco o ver si dos vrtices estn
conectados son independientes del nmero de vrtices y de arcos. Por el
contrario, existen dos grandes inconvenientes:
Es una representacin orientada hacia grafos que no modifica el nmero de
sus vrtices ya que una matriz no permite que se le o supriman filas o
columnas.
Se puede producir un gran derroche de memoria en grafos poco densos (con
gran nmero de vrtices y escaso nmero de arcos).
Para evitar estos inconvenientes se introduce otra representacin: las listas de
adyacencia.

7-

LISTAS DE ADYACENCIA.

En esta estructura de datos la idea es asociar a cada vrtice i del grafo una
lista que contenga todos aquellos vrtices j que sean adyacentes a l. De esta
forma slo reservar memoria para los arcos adyacentes a i y no para todos los
posibles arcos que pudieran tener como origen i. El grafo, por tanto, se
representa por medio de un vector de n componentes (si |V|=n) donde cada
componente va a ser una lista de adyacencia correspondiente a cada uno de
los vrtices del grafo. Cada elemento de la lista consta de un campo indicando
el vrtice adyacente. En caso de que el grafo sea etiquetado, habr que aadir
un segundo campo para mostrar el valor de la etiqueta.

36

Esta representacin requiere un espacio proporcional a la suma del nmero de


vrtices, ms el numero de arcos, y se suele usar cuando el nmero de arcos
es mucho menor que el nmero de arcos de un grafo completo. Una desventaja
es que puede llevar un tiempo O(n) determinar si existe un arco del vrtice i al
37

vrtice j, ya que puede haber n vrtices en la lista de adyacencia asociada al


vrtice i.
Mediante el uso del vector de listas de adyacencias slo se reserva memoria
para los arcos existentes en el grafo con el consiguiente ahorro de la misma.
Sin embargo, no permite que haya vrtices que puedan ser aadidos o
suprimidos del grafo, debido a que la dimensin del grafo debe ser
predeterminada y fija. Para solucionar esto se puede usar una lista de listas de
adyacencia. Slo los vrtices del grafo que sean origen de algn arco
aparecern en la lista. De esta forma se pueden aadir y suprimir arcos sin
desperdicio de memoria ya que simplemente habr que modificar la lista de
listas para reflejar los cambios.

Como puede verse en el ejemplo de las figuras anteriores tanto el vector de


listas de adyacencias como en la lista de listas se ha razonado en funcin de
los vrtices que actan como orgenes de los arcos. Anlogamente se poda
haber hecho con los vrtices destino, y combinando ambas representaciones
podra pensarse en utilizar dos vectores de listas de adyacencia o dos listas de
listas de adyacencia.

38

REPRESENTACION PROPUESTA.
La eleccin de una estructura idnea para representar el TDA grafo no es una
tarea fcil ya que existen dos representaciones totalmente contrapuestas: por
un lado tenemos la matriz de adyacencias que es muy eficiente para
comprobar si existe una arista uniendo dos vrtices pero que sin embargo
desperdicia una gran cantidad de espacio si el grafo no es completo o esta
lejos de serlo, adems no tiene la posibilidad de aadir nuevos vrtices; y por
otra parte est la lista de adyacencias que no tiene el problema de la anterior
respecto al espacio pero que sin embargo no es tan eficiente a la hora de ver si
existe una arista entre dos nodos determinados.
Teniendo en cuenta estas consideraciones se ha optado por realizar una
mezcla de ambas representaciones intentando aprovechar de alguna forma las
ventajas que ambas poseen. Por otra parte siguiendo con la idea de tratar tanto
los grafos dirigidos como los no dirigidos bajo una misma estructura, la
estructura elegida posee dos apariencias ligeramente diferentes para tratar de
forma adecuada cada uno de estos dos tipos de grafos.
La estructura consiste (en el caso de que tengamos un grafo dirigido en una
lista de vrtices donde cada uno de estos posee dos listas, una de aristas
incidentes a l y otra de adyacentes. Cada vez que se aade una arista al grafo
se inserta en la lista de aristas adyacentes del vrtice origen y en la de
incidentes del vrtice destino. De esta forma la estructura desplegada se
asemejara a una matriz de adyacencia en la cual hay una arista por cada 1 y el
ndice de la matriz es la posicin dentro de la lista de vrtices.
Grficamente la estructura para un grafo dirigido queda como se puede
apreciar en la siguiente figura. El puntero que de la estructura arco que apunta
al destino se ha sustituido por la etiqueta del nodo destino en el grafico para
simplificarlo y hacerlo mas claro.

39

Esta estructura no seria la mas idnea si trabajamos con solo con grafos no
dirigidos ya que por cada arista no dirigida tendramos que insertar en la
estructura una misma arista dirigida repetida dos veces (una con un vrtice
como origen y el otro como destino y al contrario). En muchos problemas si
asumimos el desperdicio de espacio podra, de todas formas, resultar
interesante representar un grafo no dirigido como un grafo dirigido simtrico, el

40

problema se presenta cuando al tener dos aristas dirigidas esto supone la


presencia de un ciclo en el grafo que realmente no existe.
Teniendo en cuenta el razonamiento anterior, en el caso de que queramos
manejar grafos no dirigido la estructura consistira en tener una lista de
adyacencia para cada uno de los vrtices pero tratando aquellas aristas que
aparecen en la lista de adyacencia de dos vrtices distintos y que unen ambos
vrtices como una nica arista lgica (a estas dos aristas que forman una
misma arista lgica las llamaremos aristas gemelas).

41

8-

ADT CONJUNTO

Conjunto es tambin un tipo de dato estructurado, puede definirse como una


coleccin de objetos del mismo tipo de base. El tipo de base puede ser
solamente un tipo de dato ordinal (enteros, caracteres, enumerados y
subrango).
Normalmente el limite del numero de elementos de los conjuntos lo fija la
computadora en la cual se esta trabajando, de cualquier manera el numero
debe ser finito y es recomendable que sea lo mas pequeo posible.
Los conjuntos sern definidos de la siguiente manera:
Ident_conjunto = CONJUNTO de tipo_base
Donde: tipo_base es cualquier tipo ordinal de dato.
Ejemplo: se definen los conjuntos NUMEROS, MAYUSCULAS y ALUMNOS.
NUMERO es el tipo de conjunto formado por todos los nmeros enteros
comprendidos entre el 1 y el 50 inclusive. MAYUSCULAS es el tipo conjunto
formado por todas las letras maysculas y finalmente ALUMNO es el tipo
conjunto formado por todos los elementos del tipo enumerado NOMBRES.
NOMBRES= (Juan, Jos, Julio, Javier)
NUMEROS= CONJUNTO de 150
MAYUSCULAS= CONJUNTO A Z
ALUMNOS= CONJUNTO de Nombres
Los parntesis cuadrados[ ] se usan para indicar un conjunto, por lo tanto el
conjunto vaci se representara como [ ].

Considerando los conjuntos definidos en el ejemplo anterior, se presentan a


continuacin ejemplos de asignaciones a variables de tipo conjunto:
42

CONJLETRAS

[A, E, I, O, U]

CONJALUM

[JUANJAVIER]

CONJNUM

[1..10]

En el premier ejemplo se asigna a la variable CONJLETRAS el conjunto


formado por las vocales maysculas. En el segundo ejemplo se le asigna todo
tipo de base, ya que la variable CONJALUM se le asigna el conjunto formado
por todos los elementos del tipo nombre. El tercer ejemplo solo se le asigna el
conjunto formado por los 10 primeros enteros comprendidos entre 1 y 10.
OPERACIONES CON CONJUNTOS.
Operadores bsicos que se cuentan para trabajar con conjuntos:
OPERADOR
+
*

EXPRESION
A+B
A*B

RESULTADO
Es un conjunto (es la unin de los conjuntos A y B)
Es un conjunto (es la interseccin de los conjuntos

A-B

A y B)
Es un conjunto (es la diferencia entre los conjuntos

A=B

A y B)
Valor booleano (verdadero si el conjunto A es igual

AB

al conjunto B)
Valor booleano (verdadero si el conjunto A no es

A<=B

igual al conjunto B)
Valor booleano (verdadero si el conjunto A es un

A>=B

subconjunto del conjunto B)


Valor booleano (verdadero si el conjunto B es un

<=
>=

AB

subconjunto del conjunto A)


Valor booleano (verdadero si el elemento a
pertenece al conjunto B)

Existe una cierta jerarqua o prioridad entre los operadores de conjuntos. La


interseccin (*) es la operacin de mas alta prioridad, luego le sigue la unin (+)
y la diferencia (-), por ultimo los operadores relacionales y el operador de
pertenencia.

43

En el siguiente ejemplo se pueden observar como funcionan estos operadores:


Sean los conjuntos A, B, C y E del tipo numero, se le asignaran valores a cada
uno de ellos.
A

[110]

[2,4,6,8,10]

[1,3,5,7,9]

[20, 30, 40]

En la columna izquierda de la tabla siguiente se presentan algunas operaciones


con los conjuntos citados y en la columna derecha los resultados obtenidos:
OPERACIN
E
A+D
E
B+C
E
A*C

RESULTADO
E=[1..10,20,30,40]
E=[1..10] = A
E=[1,3,5,7,9] = C

44

E
B*C
E
A-B
E
A-C
(20D)
(20A)
(4C)
(4B)
(8 (B*A))
(3 (A-B))
(3 (A-C))
(A>=B)
(A>=D)
((B*C)= [ ])
(A<=C)
(C<=A)
((B+C))A

E=[ ] = CONJUNTO VACIO


E=[1,3,5,7,9] = C
E=[2,4,6,8,10] = B
VERDADERO
FALSO
FALSO
VERDADERO
VERDADERO
VERDADERO
FALSO
VERDADERO
FALSO
VERDADERO
FALSO
VERDADERO
FALSO

REPRESENTACION EN MEMORIA
Un conjunto se representa en memoria por medio de su funcin caracterstica
la cual es un arreglo cuyo elemento i-esimo indica la presencia o ausencia del
elemento i en el conjunto (considerando el tipo base). El tamao del arreglo lo
determina el cardinal del tipo base del conjunto.

EJEMPLO:
Sea CONJ el conjunto formado por los nmeros entre 1 y 10 inclusive:
CONJ = CONJUNTO DE 1:10
Supngase ahora que se asigna a CONJ los valores del 1 al 5:
CONJ [15]
La representacin en memoria del conjunto CONJ ser una secuencia de bits
donde cada bits indicara la presencia o ausencia de un elemento en CONJ. En
este caso la representacin queda como se muestra en la siguiente figura:

45

El 1 en la posicin i indica que el elemento i pertenece a CONJ


1 1 1 1 1 0 0 0 0 0
1 2 3 4 5 6 7 8 9 10
El 0 en la posicin i indica que el elemento i no pertenece a CONJ

REPRESENTACION INTERNA DE CONJUNTOS.


Al utilizar este tipo de representaciones en las operaciones de unin, diferencia
e interseccin se puede implementar por medio de operadores lgicos; lo cual
tiene la ventaja de que todas las computadoras cuentan con dichas
operaciones y adems operan simultneamente con todos los elementos (bits)
de una palabra.
El numero mximo de elemento de un tipo base esta limitado por el tamao de
la palabra de la computadora.

46

9-

El TAD PILA

Bsicamente es una lista en la cual todas las operaciones de insercin y


borrado se producen en uno de los extremos de la lista. Un ejemplo grafico es
una pila de libros en un cajn. A medida que vamos recibiendo ms libros los
ubicamos en la parte superior. En todo momento tenemos acceso slo al libro
que se encuentra sobre el tope de la pila. Si queremos acceder a algn libro
que se encuentra ms abajo (digamos en la quinta posicin desde el tope)
debemos sacar los primeros cuatro libros y ponerlos en algn lugar para poder
acceder al mismo. La Pila es el tpico ejemplo de la estructura tipo
LIFO (por Last In First Out, es decir el ltimo en entrar es el primero en
salir).

Las pilas son estructuras lineales que tienen restricciones en cuanto a la


posicin en la cual puede realizarse la insercin y eliminacin de elementos.
Una Pila es una lista de elementos a la cual se puede insertar o eliminar
elementos slo por uno de los extremos. En consecuencia, los elementos de
una pila sern eliminados en orden inverso al que se insertaron. Es decir, el
ltimo elemento que se mete en la pila es el primero que se saca. Existen
numerosos casos prcticos en los que se utiliza el concepto de pila:
Ejemplos:
Pila de Platos.

Puede verse una pila de platos. Es de suponerse que el cocinero, si necesita


un plato limpio, tomar el que est encima de todos, que es el ltimo que se
coloco en la pila.

47

Pila de latas en un supermercado:

Debido al orden en el cual se insertan o eliminan elementos de una pila, a esta


estructura tambin se le conoce como estructura LIFO (Last-In, First-Out:
ultimo en entrar, primero en salir).
Las pilas pertenecen al grupo de estructuras de datos, lineales, ya que los
componentes ocupan lugares sucesivos en la estructura.

REPRESENTACION DE PILAS:
Las pilas no son estructuras fundamentales de datos, es decir, no estn
definidas como tales en los lenguajes de programacin (como lo estn por
ejemplo los arreglos). Las pilas pueden representarse mediante el uso de:

Arreglos

Listas Enlazadas.

Aqu se utilizarn arreglos. En consecuencia deber definirse cul ser el


tamao mximo de la pila, y adems una auxiliar a la que se denominar TOPE
que ser un apuntador al ltimo elemento insertado en la pila.
En el siguiente ejemplo se presentan dos alternativas de representacin de una
pila, utilizando arreglos.

48

Pila
Max

Tope

4
3
2
1

444
333

222
111

111
1

222

333

444

Max

tope

Representacin de pilas.
Al utilizar arreglos para implementar pilas se tiene las limitaciones de memoria
reservada, propia de los arreglos. Una vez dado un mximo de capacidad a la
pila, no es posible insertar un nmero de elementos mayor al mximo fijado. Si
la pila estuviera llena y se intentara insertar un nuevo valor, se tendra un error
conocido con el nombre de desbordamiento (overflow). Por ejemplo, si en la
pila presentada en la figura a), donde TOPE = MAX, que se quisiera insertar
otro elemento, se tendra un error de desbordamiento. La pila est llena y el
espacio reservado de memoria es fijo. No puede expandirse o contraerse.

a)
Tope

999
49

...
3
2
1

333
222
111

Una posible solucin a este tipo de errores consiste en definir pilas de gran
tamao, pero esto resultara ineficiente y costoso, si slo se utilizaran algunos
elementos. No siempre es posible saber con exactitud cul es el nmero de
elementos a tratar, por lo tanto siempre la posibilidad de cometer un error de
desbordamiento (si se reserva menos especio del que efectivamente se usar)
o bien de hacer uso ineficiente de la memoria (si se reserva ms espacio del
que se emplear).
Existe otra alternativa de solucin a este problema. Consiste en usar espacios
compartidos de memoria para la implementacin de pilas. Supngase que se
necesitan dos pilas, cada una de ellas con un tamao mximo de N elementos.
Se definir un solo arreglo de 2 * N elementos, en lugar de dos arreglos de N
elementos cada uno.

PILA 1

PILA 2

2 3

N N+1

Tope 1

2N-1 2N

Tope 2

Representacin de pilas en espacios compartidos.

50

Ejemplo.
A)

PILA 1

PILA 2

2 3

N N+1 N+2

2N-1

Tope 1

2N

tope 2

En la figura A) la PILA 1 ocupa de la posicin 1 en adelante (1,3,), mientras


que la PILA 2 ocupa de las posiciones 2N hacia atrs (2N 1, 2N-2). Si en
algn punto del proceso la PILA 1 necesitar ms de N espacios y en ese
momento la PILA 2 no tuviera ocupados sus N lugares, entonces se podran
seguir agregando elementos a la PILA 1 sin caer en un error de
desbordamiento.
B)
PILA 1

PILA 2

2 3

N-1 N N+2

Tope 1

tope 2

2N-1

2N

En la figura B), podra ocurrir lo mismo que en la A), la PILA 2 al querer ocupar
ms espacio y que la PILA 1 tuviera lugares disponibles.

OPERACIONES CON PILAS.

51

Las operaciones elementales que pueden realizarse en una pila son:

Poner un elemento (Push)

Quitar un elemento (Pop)

Considerando que se tiene una pila que puede almacenar un mximo nmero
de elementos y que el ltimo de ellos est indicado por TOPE, los algoritmos
para poner y quitar elementos son los siguientes:
Algoritmo PONE
PONE (PILA, MAX, TOPE, DATO)
{Este algoritmo pone el elemento DATO en la pila. Actualiza el valor de TOPE,
MAX, es el mximo nmero de elementos que puede almacenar PILA}.
1. si TOPE MAX { Verifica que haya espacio libre}
Entonces
Hacer TOPE

TOPE + 1 {Actualizar TOPE} y

PILA [TOPE]

DATO

{Pone el nuevo elemento en el TOPE de PILA}


Sino
Escribir Desbordamiento
2. { Fin del condicional del paso 1}

52

Algoritmo QUITA
QUITA (PILA, TOPE, DATO)
{Este algoritmo saca el ltimo elemento de PILA y lo guarda en DATO, Actualiza
el valor de TOPE}
1. Si TOPE 0 {Verifica que la PILA no este vaca}
Entonces.
Hacer DATO

PILA [TOPE] y

TOPE

TOPE-1 (Actualiza TOPE)

Si no
Escribir subdesbordamiento
2. {Fin del condicional del paso 1}
Ejemplos de Insercin y eliminacin.
Si se supieran los elementos lunes, martes, mircoles, jueves y viernes, en
este orden en PILA, la estructura quedara como lo indica la figura:
PILA
MAX
...
TOPE

5
4
3
2
1

Viernes
Jueves
Mircole

Martes
Lunes

53

Ahora bien, si se quitara viernes el TOPE apuntara a jueves

como en el

siguiente ejemplo:
PILA
MAX
...
TOPE

4
3
2
1

Viernes
Jueves
Mircole

Martes
Lunes

Si se pretendiera eliminar martes, antes deberan quitarse jueves y mircoles,


para que quede martes en la cima de PILA y pueda extraerse.

PILA
MAX

PILA

PILA

MAX

MAX

TOPE 3
2
1

Mircoles

Martes

TOPE

Martes
Lunes

Lunes

54

TOPE 1
Lunes

PRACTICA DE PILAS:
1. Realizar el programa de pilas con las operaciones push, pop, insertar, ver.
PROGRAMA DE PILAS:
# include <conio.h>
# include <iostream.h>
# include <stdlib.h>
# include <sprinf.h>
Class pila
{
Int items [50];
Int top;
Public:
Void push (int x);
Void pop ( );
Void iniciar ( );
Void ver ( );
};
Void pila :: iniciar ( )
{
Top = 1;
}
Void pila :: push (int x)
{
++ top;
Items [top] = x;
Return;
}

55

Void pila :: pop (int x)


{
-- top;
Items [top] = x;
Return;
}
Void pila :: ver ( )
{
If (top < 0)
Cout << endl << pila vaca;
Else
{
Cout << el contenido de la pila es: ;
For (i = 0; i <= top; ++ i)
Cout <<

<< tems [i];

}
return;
}
Void main ( )
{
Pila p;
Int sel, x;
p. iniciar ( )
clrscr ( );
do
{
Cout << Men principal;
Cout << endl;
Cout << 1. Agregar datos;
Cout << endl;
Cout << endl << 2. Remover datos;
Cout << endl << 3. ver datos de pila;
Cout << 4. salir;
56

Cout << endl << Elija una opcin:;


Cin >> sel;
Switch (sel)
{

Case 1:
Cout << Introduzca los datos:;
Cin >> x;
P. push (x);
Break;
Case 2:
p. pop ( );
getch ( );
break;
Case 3:
p. ver ( );
getch ( );
break;
Case 4:
-exit (0);
Default:
Cout << endl << error de seleccin;
Getch ( );

}
} while (sel = 4);
Getch ( );
}
3. Probar dicho programa con los siguientes juegos de datos:
1.

b) Insertar 23,56,78,34,67

2.

b) Ver los elementos de la pila

3.

b) eliminar de la pila

4.

b) Ver los elementos de la pila

5.

b) insertar los elementos 12.6,55

6.

b) Ver los elementos de la pila.


57

APLICACIONES.
Las pilas son una estructura de datos muy usada en la solucin de diversos
tipos de problemas. Entre estos es usado en:
Llamadas a subprogramas.
Recursin
Tratamiento de Expresin Aritmtica
Ordenacin.

Llamadas a Subprogramas:

Cuando se tiene un programa internamente se usan pilas para guardar el


estado de las variables del programa en el momento que se hace la llamada.
As, cuando termina la ejecucin del subprograma, los valores almacenados en
la pila pueden recuperarse para continuar con la ejecucin del programa en el
punto en el cual fue interrumpido.

Permiten guardar la direccin del programa (Subprograma) desde


donde se hizo la llamada a otros subprogramas, para poder regresar y seguir
ejecutndolo a partir de la instruccin inmediata a la llamada.

Permiten guardar el estado de la variable en el momento que se


hace la llamada para poder seguir ocupndolas al regresar del subprograma.

Recursin:

Tratamiento de expresiones aritmticas:


Un problema interesante es poder convertir expresiones en notacin infija a su
equivalente en notacin Postfija (o prefija),

Dada la expresin A+B se dice que es notacin infija, y su nombre


se debe a que el operador (+) esta entre los operadores (A y B).

Dada la expresin AB+ se dice que es notacin postfija, debido a


que el operador (+) esta despus de los operadores (A y B).

58

Dada la expresin +AB se dice que es una notacin prefija, ya


que el operador (+) esta antes de los operadores (A y B).
La ventaja de usar expresiones en notacin polaca postfija o prefija radica en
que no son necesarios los parntesis para indicar orden de operacin, ya que
ste queda establecido por la ubicacin por la ubicacin de los operadores con
respecto a los operndoos.
Requisitos que se deben cumplir:

Solamente se manejarn los siguientes operadores (estn


ordenadamente de mayor a menor segn su prioridad de ejecucin):
(potencia).
*/ (Multiplicacin y divisin)
+- (suma y resta).

Los operadores de ms alta calidad se ejecutan primero.

Si hubiera en una expresin dos o ms operadores de igual


prioridad, entonces se procesarn de izquierda a derecha.

Las sub-expresin patentizadas tendrn ms prioridad que


cualquier operador.

59

EJEMPLO:
Expresin infija: X + Z * W
PASO
0
1
2

EXPRESIN
X+Z*W
X+ZW*
XZW*+

El primer operador que se procesa durante la traduccin de la expresin es la


multiplicacin, paso 1, debido a que es el de ms prioridad, se coloca el
operador de tal manera que los operandos afectados por l lo precedan. Para
el operador de suma se sigue el mismo criterio, los dos operandos lo preceden.
En este caso el primer operando es X y el segundo es Z W *.
Algoritmo: Conv-postfija.
CONV- POSTFIJA (EI EPOS)
{Este algoritmo traduce una expresin infija EI a postfija EPOS, haciendo uso
de una pila PILA}
{TOPE es una variable de tipo entero}
1. Hacer TOPE

2. Repetir mientras EI sea diferente de la cadena vaca


Tomar el smbolo ms a la izquierda de EI recortando luego la expresin
3.1.

Si el smbolo es parntesis izquierdo


Entonces {poner smbolo en pila}
Hacer TOPE

TOPE + 1 y

PILA [TOPE]

smbolo

Sino
3.1.1.

Si smbolo es parntesis derecho


Entonces

2.1.1.1

Repetir mientras PILA [TOPE] parntesis izquierdo

Hacer EPOS
TOPE

PILA [TOPE] y
TOPE 1
1.1.2. {Fin del ciclo del paso 2.1.1.1}
60

{Sacamos el parntesis izquierdo de PILA y no lo agregamos


a EPOS}
Hacer TOPE

TOPE 1

Sino
2.1.1.3. Si smbolo es un operando
Entonces
Agregar smbolo a EPOS
Sino {Es un operador}
2 1.1.3. A Repetir mientras TOPE 0 y la prioridad del operador sea menor o
igual que la prioridad del operador que est en la cima de PILA
Hacer EPOS
TOPE

EPOS + PILA [TOPE]


TOPE 1

2.1.1.3. B. {Fin del ciclo del paso 2.1.1.3. A}


Hacer TOPE
PILA [TOPE]

TOPE + 1 y
smbolo

2.1.1.4 {Fin del condicional del paso 2.1.1.3}


3.1.2.

{fin del condicional del paso 2.1.1}

3.2.

{fin del condicional del paso 2.1}

4.

{Fin del ciclo del paso 2}

5.

Repetir mientras TOPE > 0


Hacer EPOS

EPOS+ PILA [TOPE] Y TOPE

6.

{fin del ciclo del paso 4}

7.

Escribir EPOS.

61

TOPE-1

ALGORITMO PARA CONVERTIR UNA EXPRESION INFIJA A PREFIJA:


1.

Introducir un parntesis derecho en la pila.

2.

Examinar la expresin de derecha a izquierda y repetir hasta que la pila


quede vaca.

3.

Si se lee un operando:

a)

Aadir a la expresin prefija.

4.

Si se lee un parntesis derecho ).


a) Introducirlo a la pila.

5.

Si se lee un operador:

a)

Repetidamente sacar de la pila y aadirlo a la expresin prefija cada


operador que tenga mayor precedencia que el operador examinado.

b)

Introducir el operador examinado en la pila.

6.

Si se lee un parntesis izquierdo (.

a)

Repetidamente sacar de pila y aadir a la expresin prefija cada


operador hasta encontrar un parntesis derecho y eliminarlo de la pila.

62

Convierta la expresin Q a prefija utilizando el algoritmo


Q= (X+T*Z/(M-N*K)%O)
N
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

EXAMINAR
)
0
%
)
K
*
N
M
(
/
Z
*
T
+
X
(
(

PILA
)
))
))
))%
))%)
))%)
))%)*
))%)*
))%)))%)))%
))%/
))%/
))%/*
))%/*
))+
))+
)
VACIO

EXPRESIN
O
O
O
OK
OK
OKN
OKN*
OKN*M
OKN*MOKN*MOKN*M-Z
OKN*M-Z
OKN*M-ZT
0KN*M-ZT*/%
OKN*M-ZT*/%X
OKN*M-ZT*/%X+
OKN*M-ZT*/%X+

63

Algoritmo de transferencia de expresiones infijas en expresiones


postfijas.
Suponiendo que Q es una expresin escrita en notacin infija. Este algoritmo
encuentra la expresin postfija P equivalente.
1-

Ingresar (en la pila y agregar ) al final de Q.

2-

Examinar Q de izquierda a derecha y repetir pasos 3 a 6 para


cada elemento de Q hasta que pila este vaca.

3-

Si se encuentra un operando agregarlo a P.

4-

Si se encuentra un parntesis izquierdo, ingresarlo en pila.

5-

Si se encuentra un operador entonces:

6-

a) repetidamente sacar de pila y aadir a P cada operador hasta


que tenga la misma precedencia o mayor que 0.

7-

b) Aadir 0 a pila.

8-

Fin de la condicional.

9-

Si se encuentra el parntesis derecho entonces:

10-

A) repetidamente sacar de pila y aadir a P cada operador hasta


que se encuentre un parntesis izquierdo.

11-

B) Eliminar el parntesis izquierdo [no aadir el parntesis


izquierdo a P]

12-

Fin del condicional

13-

Fin bucle del paso 2.

14-

Salir.

64

Ejemplo: Dada la siguiente expresin Infija, obtenga la Postfija siguiendo el


algoritmo:
Q=(X+T*Z/(M-N*K)%O)
N
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Smbolo examinado
(
X
+
T
*
Z
/
(
M
N
*
K
)
%
O

Pila
(
((
((
((+
((+*
((+*
((+/
((+/(
((+/(
((+/(((+/(((+/
((+/
((+/
((+%
((+%

Expresin P
X
X
XT
XT
XTZ
XTZ*
XTZ*
XTZ*M
XTZ*M
XTZ*MN
XTZ*MN
XTZ*MNK
XTZ*MNK*XTZ*MNK*-/
XTZ*MNK*-/O

CLASE PRCTICA:
Sobre las aplicaciones de las pilas en la conversin de expresiones

resuelva.

Dada las siguientes expresiones Infijas aplicar el algoritmo para convertir


a expresiones prefijas.
A)

((a+b/c)*(5*42)-7)
((8/3*4)-(52-4)*9)

B)

Dadas las siguientes expresiones infijas aplicar el algoritmo para


convertir a expresiones postfijas.

a)

((X2)+(4*X)-3)

b) ((273)+(8*X2)+3)

65

10-

El TAD COLA

Por contraposicin con la pila, la cola es un contenedor de tipo FIFO (por First
In First Out, el primero en entrar es el primero en salir). El ejemplo clsico es la
cola de la caja en el supermercado. La cola es un objeto muchas veces usado
como buffer o pulmn, es decir un contenedor donde almacenar una serie de
objetos que deben ser procesados, manteniendo el orden en el que ingresaron.
La cola es tambin, como la pila, un subtipo de la lista llama tambin a ser
implementado como un adaptador.
Intercalacin de vectores ordenados Ejemplo:

Un problema que normalmente

surge dentro de los algoritmos de ordenamiento es el intercalamiento de


contenedores ordenados. Por ejemplo, si tenemos dos listas ordenadas L1 y L2,
el proceso de intercalamiento consiste en generar una nueva lista L ordenada
que contiene los elementos en L1 y L2 de tal forma que L est ordenada, en la
forma lo ms eficiente posible. Con listas es fcil llegar a un algoritmo O(n)
simplemente tomando de las primeras posiciones de ambas listas el menor de
los elementos e insertndolo en L (ver secciones 4.3.0.2 y 5.6). Adems, este
algoritmo no requiere memoria adicional, es decir, no necesita alocar nuevas
celdas ya que los elementos son agregados a L a medida que se eliminan de L1
y L2 (Cuando manipula contenedores sin requerir memoria adicional se dice que
es in place (en el lugar)). Para vectores el problema podra plantearse as.
Consigna: Sea a un arreglo de longitud par, tal que las posiciones pares
como las impares estn ordenadas entre si, es decir:
a0 a2 an2 a1 a3 an1
Escribir un algoritmo que ordena los elementos de a.

66

Ordenamiento por insercin


Solucin: Consideremos por ejemplo que el arreglo contiene los siguientes
elementos.
a = 10 1 12 3 14 5 16 7 18 9 20 51 22 53 24 55 26 57 28 59
Verificamos que los elementos en las posiciones pares ( 10 12 14 16 . . . )
estn ordenados entre si, como tambin los que estn en las posiciones
impares (

. . . ). Consideremos primero el algoritmo de

ordenamiento por insercin (ver cdigo 2.19, los algoritmos de ordenamiento


seran estudiados en ms detalle en un capitulo posterior).

void inssort (vector<int> &a) {

int n=a.size();

for (int j=1 ; j<n; j++) {

int x = a[j];

int k = j;

while (--k>=0 && x<a[k]) a[k+1 ] = a[k];

a[k+1 ] = x;

8
9

}
}

67

11-

RBOLES GENERALES

Hasta ahora las estructuras de datos que hemos estudiado eran de tipo lineal,
o sea, exista una relacin de anterior y siguiente entre los elementos que la
componan (cada elemento tendr uno anterior y otro posterior, salvo los casos
de primero y ltimo).Pues bien, aqu se va a estudiar una estructuracin de los
datos ms compleja: los rboles.
Este tipo de estructura es usual incluso fuera del campo de la informtica. El
lector seguramente conoce casos como los rboles gramaticales para analizar
oraciones, los rboles genealgicos, representacin de jerarquas, etc...La
estructuracin en rbol de los elementos es fundamental dentro del campo de
la informtica aplicndose en una amplia variedad de problemas como veremos
ms adelante.
En principio podemos considerar la estructura de rbol de manera intuitiva
como una estructura jerrquica. Por tanto, para estructurar un conjunto de
elementos ei en rbol, deberemos escoger uno de ellos e 1 al que llamaremos
raz del rbol. Del resto de los elementos se selecciona un subconjunto e 2,...,ek
estableciendo una relacin padre-hijo entre la raz y cada uno de dichos
elementos de manera que e1 es llamado el padre de e 2,de e3,...ek y cada uno de
ellos es llamado un hijo de e1. Interactivamente podemos realizar la misma
operacin para cada uno de estos elementos asignando a cada uno de ellos un
nmero de 0 o ms hijos hasta que no tengamos ms elementos que insertar.
El nico elemento que no tiene padre es e 1,la raz del rbol. Por otro lado hay
un conjunto de elementos que no tienen hijos aunque s padre que son
llamados hojas. Como hemos visto la relacin de paternidad es una relacin
uno a muchos.
Para tratar esta estructura cambiaremos la notacin:
Las listas tienen posiciones. Los rboles tienen nodos.
Las listas tienen un elemento en cada posicin. Los rboles tienen una etiqueta
en cada nodo (algunos autores distinguen entre rboles con y sin etiquetas. Un
rbol sin etiquetas tiene sentido aunque en la inmensa mayora de los

68

problemas necesitaremos etiquetar los nodos. Es por ello por lo que a partir de
ahora slo haremos referencia a rboles etiquetados).
Usando esta notacin, un rbol tiene uno y slo un nodo raz y uno o ms
nodos hoja.
Desde un punto de vista formal la estructura de datos rbol es un caso
particular de grafo, ms concretamente, en la teora de grafos se denota de
forma similar como rbol dirigido. A pesar de ello, la definicin formal ms usual
de rbol en ciencias de la computacin es la recursiva:
El caso bsico es un rbol con un nico nodo. Lgicamente este nodo es a la
vez raz y hoja del rbol.
Para construir un nuevo rbol a partir de un nodo n r y k rboles A1 ,A2,...,Ak de
races n1,n2,...,nk con N1,N2,...,Nk elementos cada uno establecemos una
relacin padre-hijo entre nr y cada una de las races de los k rboles. El rbol
resultante de N=1 + N1 + ... + Nk nodos tiene como raz el nodo n r, los nodos
n1,n2,...,nk son los hijos de nr y el conjunto de nodos hoja est formado por la
unin de los k conjuntos hojas iniciales. Adems a cada uno de los Ai se les
denota subrboles de la raz.
Ejemplo: Consideremos el ejemplo de la siguiente figura.

Podemos observar que cada uno de los identificadores representa un nodo y la


relacin padre-hijo se seala con una lnea. Los rboles normalmente se
presentan en forma descendente y se interpretan de la siguiente forma:

E es la raz del rbol.


69

S1,S2,S3 son los hijos de E.

S1,D1 componen un subrbol de la raz.

D1,T1,T2,T3,D3,S3 son las hojas del rbol.


etc...
Adems de los trminos introducidos consideraremos la siguiente terminologa:
Grado de salida o simplemente grado. Se denomina grado de un nodo al
nmero de hijos que tiene. As el grado de un nodo hoja es cero. En la figura
anterior el nodo con etiqueta E tiene grado 3.
Caminos. Si n1,n2,...,nk es una sucesin de nodos en un rbol tal que n i es el
padre de ni+1 para 1<=i<=k-1 ,entonces esta sucesin se llama un camino del
nodo ni al nodo nk. La longitud de un camino es el nmero de nodos menos
uno, que haya en el mismo. Existe un camino de longitud cero de cada nodo a
s mismo. Ejemplos sobre la figura anterior:
E,S2,D2,T3 es un camino de E a T3 ya que E es padre de S2,ste es padre de
D2,etc.
S1,E,S2 no es un camino de S1 a S2 ya que S1 no es padre de E.
Ancestros y descendientes. Si existe un camino, del nodo a al nodo b ,entonces
a es un ancestro de b y b es un descendiente de a. En el ejemplo anterior los
ancestros de D2 son D2,S2 y E y sus descendientes D2,T1,T2 y T3(cualquier
nodo es a la vez ancestro y descendiente de s mismo). Un ancestro o
descendiente de un nodo, distinto de s mismo, se llama un ancestro propio o
descendiente propio respectivamente. Podemos definir en trminos de
ancestros y descendientes los conceptos de raz, hoja y subrbol:
En un rbol, la raz es el nico nodo que no tiene ancestros propios.
Una hoja es un nodo sin descendientes propios.
Un subrbol de un rbol es un nodo, junto con todos sus descendientes.

70

Algunos autores prescinden de las definiciones de ancestro propio y


descendiente propio asumiendo que un nodo no es ancestro ni descendiente
de s mismo.
Altura. La altura de un nodo en un rbol es la longitud del mayor de los
caminos del nodo a cada hoja. La altura de un rbol es la altura de la raz.
Ejemplo: en la figura anterior la altura de S2 es 2 y la del rbol es 3.
Profundidad. La profundidad de un nodo es la longitud del nico camino de la
raz a ese nodo. Ejemplo: en la figura anterior la profundidad de S2 es 1.
Niveles. Dado un rbol de altura h se definen los niveles 0...h de manera que
el nivel i est compuesto por todos los nodos de profundidad i.
Orden de los nodos. Los hijos de un nodo usualmente estn ordenados de
izquierda a derecha. Si deseamos explcitamente ignorar el orden de los dos
hijos,

nos

referiremos

un

rbol

como

un

rbol

no-ordenado.

La ordenacin izquierda-derecha de hermanos puede ser extendida para


comparar cualesquiera dos nodos que no estn relacionados por la relacin
ancestro-descendiente. La regla a usar es que si n 1 y n2 son hermanos y n1
est a la izquierda de n2, entonces todos los descendientes de n 1 estn a la
izquierda de todos los descendientes de n2.

71

RECORRIDOS DE UN RBOL.
En una estructura lineal resulta trivial establecer un criterio de movimiento por
la misma para acceder a los elementos, pero en un rbol esa tarea no resulta
tan simple. No obstante, existen distintos mtodos tiles en que podemos
sistemticamente recorrer todos los nodos de un rbol. Los tres recorridos ms
importantes se denominan preorden, inorden y postorden aunque hay otros
recorridos como es el recorrido por niveles.
Si consideramos el esquema general de un rbol tal como muestra la figura
siguiente, los recorridos se definen como sigue:

El listado en preorden es:


Si el rbol tiene un nico elemento, dicho elemento es el listado en preorden.
Si el rbol tiene ms de un elemento, es decir, una estructura como muestra la
figura 2, el listado en preorden es listar el nodo raz seguido del listado en
preorden de cada uno de los subrboles hijos de izquierda a derecha.
El listado en inorden es:
Si el rbol tiene un nico elemento, dicho elemento es el listado en inorden.
Si el rbol tiene una estructura como muestra la figura 2, el listado en inorden
es listar el subrbol A1 en inorden, y listar el nodo raz seguido del listado en
inorden de cada uno de los subrboles hijos de izquierda a derecha restantes.

72

1.

El listado en post-orden es:


Si el rbol tiene un nico elemento, dicho elemento es el

listado en post-orden.
Si el rbol tiene una estructura como muestra la figura 2,el

listado en post-orden es listar en post-orden cada uno de los subrboles hijos


de izquierda a derecha seguidos por el nodo raz.
2.

El listado por niveles es: desde i=0 hasta la altura h del rbol,
listar de izquierda a derecha los elementos de profundidad i. Como podemos
observar, un nodo n1 aparece antes que n2 en el listado por niveles si la
profundidad de n1 es menor que la profundidad de n2 usando el orden de los
nodos definido anteriormente para el caso en que tengan la misma
profundidad.

Como ejemplo de listados veamos el resultado que se obtendra sobre el rbol


A de la figura 3.
Los resultados de los listados de preorden, postorden e inorden son los
siguientes:

Listado preorden.
A=Ar=rAvAs=rvAuAwAs= rvuAwAs=rvuwAxAyAzAs=

73

vuwxAyAzAs=rvuwxyA zAs=rvuwxyzAs
=rvuwxyzsApAq=rvuwxyzspAq=rvuwxyzspq.
Listado postorden.
A=Ar=AvAsr=AuAwvAsr= uAwvAsr=uAxAyAzwvAsr=
uxAyAzwvAsr=uxyAzwvAsr=uxyzwvAsr=
uxyzwvApAqsr=uxyzwvpAqsr=uxyzwvpqsr.
Listado inorden.
A=Ar=AvrAs=AuvAwrAs= uvAwrAs=uvAxwAyAzrAs=uvxw
AyAzrAs=uvxwyAzrAs=uvxwyzrAs= uvxwyzrApsAq=uvxwyzrpsAq=uvxwyzrpsq.
Por ltimo, el listado por niveles de este rbol es el siguiente: r,v,s,u,w,p,q,x,y,z.
Finalmente es interesante conocer que un rbol no puede, en general,
recuperarse con uno solo de sus recorridos. Por ejemplo: Dada la lista en
inorden: v w y x z r t u p s q, los rboles de la figura 4 tienen ese mismo
recorrido en inorden.

74

12-

ANEXOS UNIDAD ESTRUCTURAS DE DATOS


Programa de pila cola y lista

#include<stdio.h>
#include<stdlib.h>
#define NULL 0
/* ESTRUCTURA LISTA */
struct listnode {
char data;
struct listnode *nextPtr;
};
typedef struct listnode LISTNODE;
typedef LISTNODE *LISTNODEPTR;
/* ESTRUCTURA COLA */
struct cola_nodo {
char dato;
struct cola_nodo *nextPtr;
};
typedef struct cola_nodo COLANODO;
typedef COLANODO *COLANODOPTR;
/* ESTRUCTURA PILA.*/
struct pilaNodo {
int dato;
struct pilaNodo *nextPtr;
};
typedef struct pilaNodo PILANODO;
typedef PILANODO *PILANODOPTR;
/* FUNCIONES DE LISTA */
/* Funcion que Inserta los Caracteres. */
void insert (LISTNODEPTR *sPtr, char value)
{
LISTNODEPTR newPtr, previousPtr, currentPtr;
newPtr = malloc ( sizeof(LISTNODE));
if (newPtr != NULL) { /* is space available */
newPtr->data = value;
newPtr->nextPtr = NULL;
previousPtr = NULL;
currentPtr = *sPtr;

75

while (currentPtr != NULL && value > currentPtr->data) {


previousPtr = currentPtr;
currentPtr = currentPtr->nextPtr;
}
if (previousPtr == NULL) {
newPtr->nextPtr = *sPtr;
*sPtr = newPtr;
}
else {
previousPtr->nextPtr = newPtr;
newPtr->nextPtr = currentPtr;
}
}
else
printf("%c No se inserto.\n", value);
}
/* Elimina Elementos de Lista */
char delet(LISTNODEPTR *sPtr, char value)
{
LISTNODEPTR previousPtr, currentPtr, tempPtr;
if (value == (*sPtr)->data) {
tempPtr = *sPtr;
*sPtr = (*sPtr)->nextPtr;
free(tempPtr);
return value;
}
else {
previousPtr = *sPtr;
currentPtr = (*sPtr)->nextPtr;
while (currentPtr != NULL && currentPtr->data != value) {
previousPtr = currentPtr;
currentPtr = currentPtr->nextPtr;
}
if (currentPtr != NULL) {
tempPtr = currentPtr;
previousPtr->nextPtr = currentPtr->nextPtr;
free(tempPtr);
return value;
}
}
return '\0';
}

/* Funcion que determina si la lista esta VACIA */


int isEmpty(LISTNODEPTR sPtr)

76

{
return sPtr == NULL;
}
/* Impreme el orden de la lista */
void printList (LISTNODEPTR currentPtr)
{
if (currentPtr == NULL)
printf(" LA LISTA ESTA VACIA.\n\n");
else {
printf(" LISTA :\n");
while (currentPtr != NULL) {
printf("%c -->", currentPtr->data);
currentPtr = currentPtr->nextPtr;
}
printf(" NULL\n\n");
}
}

/* FUNCIONES DE LA ESTRUCTURA COLA.*/


void insertar_cola(COLANODOPTR *headPtr, COLANODOPTR *tailPtr,
char valor)
{
COLANODOPTR newPtr;
newPtr = malloc(sizeof(COLANODO));
if (newPtr != NULL) {
newPtr -> dato = valor;
newPtr -> nextPtr = NULL;
if(isEmpty(*headPtr))
*headPtr = newPtr;
else
(*tailPtr) -> nextPtr = newPtr;
*tailPtr = newPtr;
}
else
printf("%c NO SE INSERTO",valor);
return;
}

77

char eliminar_cola(COLANODOPTR *headPtr, COLANODOPTR *tailPtr)


{
char valor;
COLANODOPTR tempPtr;
valor = (*headPtr) -> dato;
tempPtr = *headPtr;
*headPtr = (*headPtr)-> nextPtr;
if (*headPtr == NULL)
*tailPtr = NULL;
free (tempPtr);
return valor;
}
int isEmpty_cola (COLANODOPTR headPtr)
{
return headPtr == NULL;
}
void imprimir_cola (COLANODOPTR currentPtr)
{
if (currentPtr == NULL)
printf("LA COLA ESTA VACIO.\n\n");
else {
printf("COLA : \n");
while (currentPtr != NULL) {
printf("%c ---> ", currentPtr->dato);
currentPtr = currentPtr->nextPtr;
}
printf(" NULL \n\n");
}
}
/* fUNCIONES DE PILA.*/
void push(PILANODOPTR *topePtr,int info)
{
PILANODOPTR newPtr;
newPtr = malloc(sizeof(PILANODO));
if(newPtr != NULL) {
newPtr -> dato = info;
newPtr -> nextPtr = *topePtr;
*topePtr = newPtr;
}
else
printf(" %d No se inserto.\n\n",info);

78

return;
}
int pop(PILANODOPTR *topePtr)
{
PILANODOPTR tempPtr;
int popValor;
tempPtr = *topePtr;
popValor = (*topePtr)-> dato;
*topePtr = (*topePtr)->nextPtr;
free(tempPtr);
return popValor;
}
void imprimir_pila(PILANODOPTR currentPtr)
{
if(currentPtr == NULL)
printf("LA pila esta Vacia.\n\n");
else {
printf(" PILA: \n");
while (currentPtr != NULL) {
printf("%d --> ",currentPtr -> dato);
currentPtr = currentPtr -> nextPtr;
}
printf("NULL\n\n");
}
}
int isEmpty_pila(PILANODOPTR topePtr)
{
return topePtr == NULL;
}
void menu(void)
{
printf("
\n
***** MENU PRINCIPAL ***** \n\n"
"
**** ESTRUCTURA LISTA **** \n "
" 1 Insertar CARACTERES en la LISTA.\n"
" 2 Sacar CARACTERES de la Lista. \n\n"
"
**** ESTRUCTURA COLA ****\n"
" 3 Insertar CARACTERES en la COLA. \n"
" 4 Sacar CARACTERES de la Cola.\n\n "
"
**** ESTRUCTURA PILA **** \n"
" 5 Insertar NUMEROS en la PILA.\n"
" 6 Sacar Numeros de la Pila.\n"
" 7 FIN DEL PROGRAMA.\n");
return;

79

/***** PROGRAMA PRINCIPAL *****/


void main(void)
{
/* Tipos de datos Lista.*/
LISTNODEPTR startPtr = NULL ;
int choice ;
char item ;
/* Tipos de datos Cola*/
COLANODOPTR headPtr = NULL , tailPtr = NULL ;
/* Tipos de datos Pila*/
PILANODOPTR pilaPtr = NULL;
int valor;
menu() ;
printf("\n --> OPCION: ");
scanf ("%d" ,&choice);
while (choice != 7) {
switch (choice) {
case 1:
printf(" Inserte el Caracter: ");
scanf("\n%c", &item);
insert(&startPtr, item);
printList(startPtr);
break;
case 2:
if(!isEmpty(startPtr)) {
printf(" Caracter a borrar: ");
scanf("\n%c", &item);
if (delet (&startPtr, item)) {
printf("%c BORRADO.\n", item);
printList (startPtr);
}
else
printf("%c NO ESTA EN LA LISTA.\n\n", item);
}
else
printf(" *** LISTA VACIA.\n\n");

80

break;

case 3:
printf("Digite el caracter");
scanf("\n%c",&item);
insertar_cola(&headPtr , &tailPtr , item);
imprimir_cola(headPtr);
break;
case 4:
if (!isEmpty (headPtr)){
item = eliminar_cola(&headPtr , &tailPtr);
printf("%c NO ESTA EN LA COLA.\n",item);
}
imprimir_cola(headPtr);
break;
case 5:
printf("Digite el Numero: ");
scanf("%d",&valor);
push(&pilaPtr,valor);
imprimir_pila(pilaPtr);
break;
case 6:
if(!isEmpty(pilaPtr))
printf(" Numero eliminado: %d.\n",
pop(&pilaPtr));
imprimir_pila(pilaPtr);
break;
default:
printf(" Eleccion Invalida.\n\n");
menu();
break;
}
printf("? ");
scanf("%d",&choice);
}
printf("Fin \n");
getch();
}

81

BSQUEDA DE UN ARBOL BINARIO


RECORRIDO EN ORDEN, INORDEN Y PREORDEN.
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
struct binarytree
{
struct binarytree *leftp;
struct binarytree *rightp;
int numero;
};
typedef struct binarytree treenode;
typedef treenode *arbolbinario;
void entrada(void);
int menu(void);
void insertarnodo(arbolbinario *,int);
void posorden (arbolbinario);
void preorden(arbolbinario);
void enorden(arbolbinario);
int vacio(arbolbinario);
void salida(void);
void main()
{
int i,n,numero,seleccion;
arbolbinario arbol=NULL;
entrada();
while((seleccion=menu())!=6) {
switch (seleccion) {
case 1:
clrscr();
printf("\n CANTIDAD DE NUMEROS DEL ARBOL:");
scanf ("%d",&n);
if(n==0)
break;
else
if(arbol==NULL) {
printf("\n\nDIGITE LA RAIZ:");
scanf("%d",&numero);
insertarnodo(&arbol,numero);
82

if(n==1)
break;
else {
printf("\nDIGITE LOS HIJOS:");
for(i=1;i<n;++i) {
scanf("%d",&numero);
insertarnodo(&arbol,numero);
}
}
}
else {
printf("\nDIGITE LOS HIJOS:");
for(i=0;i<n;++i) {
scanf("%d",&numero);
insertarnodo(&arbol,numero);
}
}
getch();
break;
case 2:
if(arbol!=NULL) {
printf("\n\n\t\tARBOL LIBERADO.");
arbol=NULL;
}else {
printf("\n\n\t\tNO EXISTE ELEMENTS EN EL ARBOL.");
}
getch();
break;
case 3:
if(arbol!=NULL) {
printf("\n\n\t\tEL ARBOL EN POSTORDEN:");
posorden(arbol);
}else {
printf("\n\n\t\tNO EXISTEN ELEMENTOS EN EL ARBOL.");
}
getch();
break;
case 4:
if(arbol!=NULL) {
printf("\n\n\t\tEL ARBOL EN PREORDEN:");
preorden(arbol);
}else{
printf("\n\n\t\tNO EXISTEN ELEMENTOS EN EL ARBOL.");
}

83

getch();
break;
case 5:
if(arbol!=NULL) {
printf("\n\n\t\tEL ARBOL BINARIO.");
enorden(arbol);
}else{
printf("\n\n\t\tNO EXISTE ELEMENTOS EN EL ARBOL.");
}
getch();
break;
case 6:
break;
default:
printf("\n\n\t\tERROR -->ENTER PARA CONTINUAR.\n");
getch();
}//FIN DEL SWITCH
}//FIN DEL WHILE
salida();
return;
}
void entrada(void)
{
clrscr();
gotoxy(25,9);printf("\"BIENVENIDO AL PROGRAMA DE
ARBOLES\"");
gotoxy(25,10);printf("-----------------------------------");
gotoxy(25,15);printf("ENTER PARA ENTRAR AL PROGRAMA");
getch();
return;
}
int menu(void)
{
int seleccion;
clrscr();
printf("\n\t\tMENU PRINCIPAL\n\t\t");
printf("\n\t\t1-AADIR ELEMENTOS DEL ARBOL\n\t\t");
printf("\n\t\t2-LIBERAR ELEMENTOS DEL ARBOL\n\t\t");
printf("\n\t\t3-VER RECORRIDO EN POST_ORDEN\n\t\t");
printf("\n\t\t4-VER RECORRIDO EN PRE_ORDEN\n\t\t");
printf("\n\t\t5-VER RECORRIDO EN ORDEN\n\t\t");
printf("\n\t\t6-PARA SALIR DEL PROGRAMA\n\t\t");
printf("\n\t\tINTRODUZCA SU ELECCION->\n\t\t");
scanf("%d,&seleccion");

84

return(seleccion);
}
void insertarnodo(arbolbinario *arbol,int numero)
{
if(*arbol==NULL) {
*arbol=malloc(sizeof(treenode));
if(arbol!=NULL) {
(*arbol)->numero=numero;
(*arbol)->leftp=NULL;
(*arbol)->rightp=NULL;
}else
printf("%d NO INSERTADO.NO HAY MEMORIA
DISPONIBLE.\n",numero);
}else if(numero<(*arbol)->numero)
insertarnodo(&((*arbol)->leftp),numero);
else if(numero>(*arbol)->numero)
insertarnodo(&((*arbol)->rightp),numero);
else
printf("\nDATO%d YA EXISTENTE EN EL
ARBOL\n",numero);
return;
}
void posorden(arbolbinario arbol)
{
if(arbol!=NULL) {
posorden(arbol->leftp);
posorden(arbol->rightp);
printf("%d",arbol->numero);
}
return;
}
void preorden(arbolbinario arbol)
{
if(arbol!=NULL) {
printf("%d",arbol->numero);
preorden(arbol->leftp);
preorden(arbol->rightp);
}
return;
}
void enorden (arbolbinario arbol)
{
if(arbol!=NULL) {
enorden(arbol->leftp);
printf("%d",arbol->numero);
enorden(arbol->rightp);

85

}
return;
}
int vacio(arbolbinario arbol)
{
return(arbol==NULL);
}
void salida(void)
{
clrscr();
gotoxy(25,15);printf("ENTER PARA SALIR DEL PROGRAMA ");
getch();
return;
}

86

13 Mtodos de ordenacin
Definicin:
Los mtodos de ordenacin de datos consiste en disponer un conjunto de
datos, o una estructura en un determinado orden con respecto a alguno de sus
campos.
Debido a que las estructuras de datos son utilizadas para almacenar informacin, para
poder recuperar esa informacin de manera eficiente es deseable que aquella est
ordenada. Existen varios mtodos para ordenar las diferentes estructuras de datos
bsicas.

En general los mtodos de ordenamiento no son utilizados con frecuencia, en


algunos casos slo una vez. Hay mtodos muy simples de implementar que
son tiles en los casos en dnde el nmero de elementos a ordenar no es muy
grande (ej, menos de 500 elementos).
Por otro lado hay mtodos sofisticados, ms difciles de implementar pero que
son

ms

eficientes

en

cuestin

de

tiempo

de

ejecucin.

Los mtodos sencillos por lo general requieren de aproximadamente n x n


pasos para ordenar n elementos.
Los mtodos simples son: insertion sort (o por insercin directa) selection sort,
bubble sort, y shellsort, en dnde el ltimo es una extensn al insertion sort,
siendo ms rpido. Los mtodos ms complejos son el quick-sort, el heap sort,
radix y address-calculation sort. El ordenar un grupo de datos significa mover
los datos o sus referencias para que queden en una secuencia tal que
represente un orden, el cual puede ser numrico, alfabtico o incluso
alfanumrico, ascendente o descendente.
Se ha dicho que el ordenamiento puede efectuarse moviendo los registros con
las claves. El mover un registo completo implica un costo, el cual se incrementa
conforme sea mayor el tamao del registro. Es por ello que es deseable evitar

87

al mximo el movimiento de los registros. Una alternativa es el crear una tabla


de referencias a los registros y mover las referencias y no los datos. A
continuacin se mostrarn los mtodos de ordenamiento empezando por el
ms sencillo y avanzando hacia los mas sofisticados. La eficiencia de los
algoritmos se mide por el nmero de comparaciones e intercambios que tienen
que hacer, es decir, se toma n como el nmero de elementos que tiene el
arreglo a ordenar y se dice que un algoritmo realiza O(n2) comparaciones
cuando compara n veces los n elementos, n x n = n2.

Anlisis de Algoritmos Notacin de Orden


Una funcin f(n) se define de orden O(g(n)), es decir, f(n) = O(g(n)) si existen
constantes positivas n0 y c tales que:
| f(n) | = c * <= | g(n) | , para toda n > n0
100 n3 => O(n3)
6n2 + 2n + 4 => O(n2)
1024 => O(1)
1+2+3+4+...+n-1+n= n * (n+1)/2 = O(n 2)

ORDENAMIENTO.
Uno de los procedimientos ms comunes y tiles en el procesamiento de
datos, es la clasificacin u ordenacin de los mismos. Se considera
ordenar al proceso de reorganizar un conjunto dado de objetos en una
secuencia determinada. Cuando se analiza un mtodo de ordenacin,
hay que determinar cuntas comparaciones e intercambios se realizan
para el caso ms favorable, para el caso medio y para el caso ms
desfavorable.
La ordenacin de una lista de objetos a partir de algn orden lineal,
como por ejemplo <= para el caso de los nmeros, es un problema que
suele presentarse con mucha frecuencia al intentar la solucin

88

computacional de un cierto problema. Esto justifica la necesidad de una


revisin cuidadosa del tema.
La colocacin en orden de una lista de valores se llama Ordenacin.
Por ejemplo, se podra disponer una lista de valores numricos en orden
ascendente o descendente, o bien una lista de nombres en orden
alfabtico. La localizacin de un elemento de una lista se llama
bsqueda.
Tal operacin se puede hacer de manera ms eficiente despus de que
la lista ha sido ordenada.
Existen varios mtodos para ordenamiento, clasificados en tres formas:
Intercambio, Seleccin, insercin.
En cada familia se distinguen dos versiones: un mtodo simple y directo,
fcil de comprender pero de escasa eficiencia respecto al tiempo de
ejecucin, y un mtodo rpido, ms sofisticado en su ejecucin por la
complejidad de las operaciones a realizar, pero mucho ms eficiente en
cuanto a tiempo de ejecucin. En general, para arreglos con pocos
elementos, los mtodos directos son ms eficientes (menor tiempo de
ejecucin) mientras que para grandes cantidades de datos se deben
emplear los llamados mtodos rpidos.

Clave:
Es el campo por el cual se ordena un elemento, osea es donde podemos
decidir desde donde se realiza la ordenacin.

Clasificacin segn su ubicacin:


Segn donde estn almacenados los datos a ordenar podemos decir que la
ordenacin se clasifica en:
-Interna: Arrays, listas o arbol. (Tpicamente en RAM)
La interna se produce en la memoria principal (RAM) de la
mquina, aprovechando las capacidades de acceso aleatorio que
presenta a la misma.
-Externa: En archivos en discos o cintas.

89

La clasificacin externa es necesaria cuando el nmero de objetos a


ordenar es demasiado grande para caber en la memoria principal, por lo
que se hace necesario un soporte externo de almacenamiento (disco
duro, por ejemplo).

Concepto:
Una lista de datos esta ordenada por una clave K(la cual permite ordenar la
informacin) , si la lista esta en orden con respecto a la calve anterior esta
puede ser en orden: ascendente (i<j) entonces (k[i]<=k[j])
Desendente: (i>j) entonces (k[i]>=k[j])
Hay numerosos mtodos de ordenamiento que difieren en eficiencia:
-Anlisis de algoritmo orientado a las comparaciones realizadas por cada
uno.
-Las comparaciones sern funciones de n siendo n el tamao del
vector a ordenar.
Por su importancia, nos centraremos en los esquemas de clasificacin
interna y veremos los principales algoritmos dentro de la misma,
agrupndolos segn la tcnica de ordenacin seguida (comparacin,
intercambio, seleccin, insercin, mezcla, etc), as como por la
complejidad computacional de los mismos.
En casi todos los casos, la aplicacin manual del algoritmo hace
evidente la medida del costo apropiada.
Veamos algunos de los mtodos ms representativos, atendiendo a las
tcnicas de ordenacin que los caracterizan.

Clasificacin segn su eficiencia:


Los mtodos de ordenacin segn su eficiencia se clasifican por:
-Bsicos (Directos): los que son eficaces en listas pequeas:
Burbuja e intercambio (Simples pero ineficiente)
Seleccin e Insercin (Recomendados).

90

-Avanzados: estos son eficaces en listas grandes (Shell) entre otros.


Estos son algoritmos rpidos pero mas difcil de comprender, de entender, y
difcil de programar para los estudiantes, si algunas listas ac son pequeas se
comportan de manera ineficiente.
Resumen de los mtodos de ordenacin.
Resumen de los mtodos de ordenacin
Tiempo
Tamao
Mtodo Estable? Caso peor / Caso Espacio
sugerido
promedio
1. Insercin
S
O(n2)
O(1)
n 100
2.
S
O(n2)
O(1)
n 100
Seleccin
3. Burbuja
S
O(n2)
O(1)
n 100
4.
No
O(n2) / O(n log n) O(log n) n 1000
Quicksort
5. Heapsort
No
O(n log n)
O(1)
n 1000
6.
S
O(n log n)
O(n)
n 1000
Mergesort
100 n
7. Shellsort
No
O(n1.5) / O(n1.25)
1000

METODOS DE ORDENACION
Ordenacin interna: Los datos se encuentran en memoria
(ya

sean arrays, listas, etc) y son de acceso aleatorio o directo (se

puede acceder a un determinado campo sin pasar por los anteriores).

Los mtodos de ordenacin interna se aplican principalmente a arrays


unidimensionales. Los principales algoritmos de ordenacin interna son:
Ordenacin por Intercambio:
El mtodo de intercambio se basa en comparar los elementos del
arreglo e intercambiarlos si su posicin actual o inicial es contraria
inversa a la deseada. Pertenece a este mtodo el de la burbuja
clasificado como intercambio directo. Aunque no es muy eficiente para
ordenar listas grandes, es fcil de entender y muy adecuado para
91

ordenar una pequea lista de unos 100 elementos o menos.


Una pasada por la ordenacin de burbujeo consiste en un recorrido
completo a travs del arreglo, en el que se comparan los contenidos de
las casillas adyacentes, y se cambian si no estn en orden. La
ordenacin por burbujeo completa consiste en una serie de pasadas
("burbujeo") que termina con una en la que ya no se hacen cambios
porque todo est en orden.
Ejemplo:
Supngase que estn almacenados cuatro nmeros en un arreglo con
casillas de memoria de x[1] a x[4]. Se desea disponer esos nmeros en
orden creciente. La primera pasada de la ordenacin por burbujeo hara
lo

siguiente:

Comparar el contenido de x[1] con el de x[2]; si x[1] contiene el mayor de


los nmeros, se intercambian sus contenidos.
Comparar el contenido de x[2] con el de x[3]; e intercambiarlos si fuera
necesario.
Comparar el contenido de x[3] con el de x[4]; e intercambiarlos si fuera
necesario.
Al final de la primera pasada, el mayor de los nmeros estar en x[4].
Ordenacin por Burbuja: Consiste en comparar pares de elementos
adyacentes e intercambiarlos entre s hasta que estn todos ordenados. Con el
array anterior, {40,21,4,9,10,35}:
Primera pasada:
{21,40,4,9,10,35} <-- Se cambia el 21 por el 40.
{21,4,40,9,10,35} <-- Se cambia el 40 por el 4.
{21,4,9,40,10,35} <-- Se cambia el 9 por el 40.
{21,4,9,10,40,35} <-- Se cambia el 40 por el 10.
{21,4,9,10,35,40} <-- Se cambia el 35 por el 40.
Segunda pasada:
{4,21,9,10,35,40} <-- Se cambia el 21 por el 4.
{4,9,21,10,35,40} <-- Se cambia el 9 por el 21.
92

{4,9,10,21,35,40} <-- Se cambia el 21 por el 10.


Ya estn ordenados, pero para comprobarlo habra que acabar esta segunda
comprobacin y hacer una tercera.
Si el array tiene N elementos, para estar seguro de que el array est ordenado,
hay que hacer N-1 pasadas, por lo que habra que hacer (N-1)*(N-i-1)
comparaciones, para cada i desde 1 hasta N-1. El nmero de comparaciones
es, por tanto, N(N-1)/2, lo que nos deja un tiempo de ejecucin, al igual que en
la seleccin, en O(n2).
int array[N];
int i,j,aux;
// Dar valores a los elementos del array
for(i=0;i<N-1;i++) // Hacer N-1 pasadas.
{
for(j=0;j<N-i-1;j++) // Mirar los N-i-1 pares.
{
if(array[j+1]<array[j]) // Si el elemento j+1 es menor que el elemento j:
{
aux=array[j+1]; // Se intercambian los elementos
array[j+1]=array[j]; // de las posiciones j y j+1
array[j]=aux; // usando una variable auxiliar.
}
}
}

Anlisis del algoritmo:


ste es el anlisis para la versin no optimizada del algoritmo:

93

Estabilidad: Este algoritmo nunca intercambia registros con claves


iguales. Por lo tanto es estable.

Requerimientos de Memoria: Este algoritmo slo requiere de una


variable adicional para realizar los intercambios.

Tiempo de Ejecucin: El ciclo interno se ejecuta n veces para una lista


de n elementos. El ciclo externo tambin se ejecuta n veces. Es decir, la
complejidad es n * n = O(n2). El comportamiento del caso promedio
depende del orden de entrada de los datos, pero es slo un poco mejor
que el del peor caso, y sigue siendo O(n2).
Ventajas:

Fcil implementacin.

No requiere memoria adicional.


Desventajas:

Muy lento.

Realiza numerosas comparaciones.

Realiza numerosos intercambios.

Este algoritmo es uno de los ms pobres en rendimiento. Si miramos la


demostracin nos daremos cuenta de ello.
A continuacin les presentamos la Implementacin en c de este
algoritmo:
void bubblesort(int n,int x[])
/*ordenar datos*/
{
int i,elem,temp;
for(elem=0;elem<n-1;++elem)
/*comparar elementos*/
for(i=elem+1;i<n;++i)
if( x[i] < x[elem]){
temp=x[elem];
x[elem]=x[i];

94

x[i]=temp;
}
return; }
Este tambin es uno de los mtodos de ordenacin ms simples. Su
principio de funcionamiento es la ordenacin por intercambio de objetos
adyacentes. La idea bsica es imaginar que los objetos a ordenar estn
en un arreglo "vertical" y que a tal sentido, los objetos con claves
menores son "ms ligeros" y por tanto "suben a la superficie"
primeramente. De ah, el sugerente nombre del mtodo. El arreglo es
recorrido varias veces de abajo hacia arriba. En cada pasada, ante un
par de elementos adyacentes se verifica si el ms "ligero" est debajo y,
en tal caso, se invierten. Tras haberse realizado el primer recorrido del
arreglo y haber aplicado sucesivamente lo anterior, el efecto resultante
es que el elemento "ms ligero" del arreglo, o sea el menor, ocupe la
primera posicin del mismo, o sea, alcanz la "superficie". En el
segundo recorrido la segunda clave menor ocupar la segunda posicin
del arreglo, y as sucesivamente. En el segundo recorrido, no es
necesario llegar hasta la posicin 1 del arreglo pues tenemos la certeza
de que en ella est el objeto de menor clave, en general, a partir de la
estrategia de ordenacin seguida, en el recorrido i no se intenta llegar
ms all de la posicin i del arreglo.

Figura 11.1: Desarrollo del primer recorrido


del arreglo.
Y aqu puede verse cmo queda el arreglo luego de terminado el
algoritmo:

95

Figura 11.2: Estado del arreglo tras cada recorrido.


El algoritmo sera as:
for i:=1 to n-1 do
for j:=n downto i+1 do
if A[j].clave < A[j-1].clave then Intercambia( A[j] con A[j-1] )
Es de fcil comprensin que este mtodo, para todos los casos es O(n2).
Tambin podemos percatarnos fcilmente que este mtodo es estable.

Algoritmo codificado en C que permite ordenar datos con el


mtodo de la burbuja.
// Ordenamiento de una secuencia de 10 nmeros.
void main()
{
int N[10];
int i, j, aux;
for(i = 1; i <= 10; i++)
scanf("%d", &N[i]);
for(i = 9; i >= 1; i--)
for(j = 1; j <= i; j++)
if(N[j] > N[j+1])
{
aux = N[j];
N[j] = N[j+1];
N[j+1] = aux;
}
for(i = 1; i <= 10; i++)
printf("%d", N[i]);
return;
}

Insercin directa: En este mtodo lo que se hace es tener una sublista


ordenada de elementos del array e ir insertando el resto en el lugar adecuado

96

para que la sublista no pierda el orden. La sublista ordenada se va haciendo


cada vez mayor, de modo que al final la lista entera queda ordenada. Para el
ejemplo {40,21,4,9,10,35}, se tiene:
{40,21,4,9,10,35} <-- La primera sublista ordenada es {40}.
Insertamos el 21:
{40,40,4,9,10,35} <-- aux=21;
{21,40,4,9,10,35} <-- Ahora la sublista ordenada es {21,40}.
Insertamos el 4:
{21,40,40,9,10,35} <-- aux=4;
{21,21,40,9,10,35} <-- aux=4;
{4,21,40,9,10,35} <-- Ahora la sublista ordenada es {4,21,40}.
Insertamos el 9:
{4,21,40,40,10,35} <-- aux=9;
{4,21,21,40,10,35} <-- aux=9;
{4,9,21,40,10,35} <-- Ahora la sublista ordenada es {4,9,21,40}.
Insertamos el 10:
{4,9,21,40,40,35} <-- aux=10;
{4,9,21,21,40,35} <-- aux=10;
{4,9,10,21,40,35} <-- Ahora la sublista ordenada es {4,9,10,21,40}.
Y por ltimo insertamos el 35:
{4,9,10,21,40,40} <-- aux=35;
{4,9,10,21,35,40} <-- El array est ordenado.
En el peor de los casos, el nmero de comparaciones que hay que realizar es
de N*(N+1)/2-1, lo que nos deja un tiempo de ejecucin en O(n2). En el mejor
caso (cuando la lista ya estaba ordenada), el nmero de comparaciones es N2. Todas ellas son falsas, con lo que no se produce ningn intercambio. El
tiempo de ejecucin est en O(n).
97

El caso medio depender de cmo estn inicialmente distribuidos los


elementos. Vemos que cuanto ms ordenada est inicialmente ms se acerca
a O(n) y cuanto ms desordenada, ms se acerca a O(n2).
El peor caso es igual que en los mtodos de burbuja y seleccin, pero el mejor
caso es lineal, algo que no ocurra en stos, con lo que para ciertas entradas
podemos tener ahorros en tiempo de ejecucin.
int array[N];
int i,j,aux;
// Dar valores a los elementos del array
for(i=1;i<N;i++) // i contiene el n ero de elementos de la sublista.
{
// Se intenta aadir el elemento i.
aux=array[i];
for(j=i-1;j>=0;j--) // Se recorre la sublista de atr a adelante para buscar
{
// la nueva posicion del elemento i.
if(aux>array[j]) // Si se encuentra la posici:
{
array[j+1]=aux; // Ponerlo
break;
// y colocar el siguiente n ero.
}
else
// si no, sigue busc dola.
array[j+1]=array[j];
}
if(j==-1) // si se ha mirado todas las posiciones y no se ha encontrado la
correcta
array[0]=aux; // es que la posici es al principio del todo.
}
Tambin se basa en el intercambio de elementos adyacentes. Este
mtodo se caracteriza porque en el i-simo recorrido se "inserta" el isimo elemento A[i] en el lugar correcto entre A[1], A[2], .... , A[i-1], los
cuales fueron previamente ordenados. Despus de esta insercin,
quedarn entonces ordenados los objetos ubicados en A[1], A[2], ... ,
A[i]. En cada pasada, el elemento A[i] se baja hasta la posicin que le
corresponde, por medio de intercambios sucesivos. La bsqueda se
detiene cuando el elemento con que se compara es menor o igual que el
A[i]. Esto ltimo garantiza la estabilidad del mtodo. La siguiente

98

secuencia de esquemas ilustra las diferentes pasadas del algoritmo para


el ejemplo.

Figura 11.4: Pasos del algoritmo de ordenacin por insercin.


Una propuesta elemental del algoritmo es la siguiente:
for i:=2 to n do
mover A[i] hacia la posicin j i tal que
(A[i] < A[k] j k < i) y (A[i] A[j-1] j=1)
Otra variante mas detallada:
for i:=2 to n do
begin
j := i;
while A[j-1] > A[j] y j>1 do
begin
intercambia (A[j] con A[j-1]);
j := j-1
end
end
Una nueva variante donde se evita el intercambio de elementos para
ubicar el j-simo elemento en la posicin correcta en el subarreglo
ordenado A[1..j-1]:

99

for j=2 to n do
begin
key = A[j]
i=j1
while i > 0 and A[i] > key do
begin
A[i + 1] = A[i]
i=i-1
end
A[i + 1] = key
end
Este mtodo en cualquiera de sus variantes es O(n) para el caso mejor y
O(n2) para el caso promedio y para el caso peor. Ya habamos
comentado que era estable.
Como hemos visto los tres mtodos estudiados tienen un tiempo de ejecucin
O(n2) y consumirn un tiempo (n2) en una buena parte de las secuencias de
entrada de n elementos.
Insercion
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#define N 10000
int a[N];
void insercion()
{
int ifi = 0, pos, num, imov;
srand(time(0));
for(; ifi<N; ifi++)
{
num = rand() % 20000;
pos = 0;
while(pos ifi && num > a[pos])
pos = pos + 1;
imov = ifi;
while(imov > pos)
{
a[imov] = a[imov-1];
imov = imov - 1;
}
a[pos] = num;
}
return;
}

100

void main(void)
{
srand(time(0));
for(int i = 0; i < N; i++)
a[i] = random(20000);
printf("Arreglo desordenado.\n");
for(i = 0; i < N; i++)
printf("%d\n", a[i]);
getch();
insercion();
printf("Arreglo ordenado.\n");
for(i = 0; i < N; i++)
printf("%d\n", a[i]);
getch();
}

Insercin binaria
Es el mismo mtodo que la insercin directa, excepto que la bsqueda del
orden de un elemento en la sublista ordenada se realiza mediante una
bsqueda binaria (ver algoritmos de bsqueda), lo que en principio supone un
ahorro de tiempo. No obstante, dado que para la insercin sigue siendo
necesario un desplazamiento de los elementos, el ahorro, en la mayora de los
casos, no se produce, si bien hay compiladores que realizan optimizaciones
que lo hacen ligeramente ms rpido.

101

Ordenacin por Seleccin


Este mtodo consiste en buscar el elemento ms pequeo del array y ponerlo
en primera posicin; luego, entre los restantes, se busca el elemento ms
pequeo y se coloca en segudo lugar, y as sucesivamente hasta colocar el
ltimo elemento. Por ejemplo, si tenemos el array {40,21,4,9,10,35}, los pasos
a seguir son:
{4,21,40,9,10,35} Se coloca el 4, el ms pequeo, en primera posicin : se
cambia el 4 por el 40.
{4,9,40,21,10,35} Se coloca el 9, en segunda posicin: se cambia el 9 por el 21.
{4,9,10,21,40,35} <-- Se coloca el 10, en tercera posicin: se cambia el 10 por
el 40.
{4,9,10,21,40,35} <-- Se coloca el 21, en tercera posicin: ya est colocado.
{4,9,10,21,35,40} <-- Se coloca el 35, en tercera posicin: se cambia el 35 por
el 40.
Si el array tiene N elementos, el nmero de comprobaciones que hay que hacer
es de N*(N-1)/2, luego el tiempo de ejecucin est en O(n2)
int array[N];
int i,j,menor,aux;
// Dar valores a los elementos del array
for(i=0;i<N-1;i++)
{
for(j=i+1,menor=i;j<N;j++)
if(array[j]<array[menor]) // Si el elemento j es menor que el menor:
menor=j; // el menor pasa a ser el elemento j.
aux=array[i]; // Se intercambian los elementos
array[i]=array[menor]; // de las posiciones i y menor
array[menor]=aux; // usando una variable auxiliar.
}
102

En la primera pasada se halla el mnimo de los n elementos y se coloca


en la primera posicin del arreglo. En la segunda pasada se halla el
mnimo de los n-1 elementos restantes y se coloca en la segunda
posicin, y as sucesivamente, hasta que solo se considere una lista en
la que queden solo dos elementos a ordenar lo cual se logra
comparando los mismos en intercambindolos si fuera necesario.
Siguiendo esta estrategia, nos damos cuenta que tras i pasadas, los i
elementos menores estarn ordenados y ocuparan las posiciones
A[1] ... A[i] del arreglo.
El siguiente esquema ilustra el mtodo:

Figura 11.3: Pasos del algoritmo de mnimos sucesivos.


Una primera aproximacin al algoritmo sera la siguiente:
for i:=1 to n-1 do
Hallar el mnimo entre A[i], ... ,A[n];
if necesario then intercambiarlo con A[i];
Una versin ms completa sera la siguiente:
Begin
for i:=1 to n-1 do
begin
{hallar el mnimo entre A[i]...A[n] e intercambiarlo con A[i]}
indice_del_menor := i;
clave_del_menor := A[i].clave;
for j:=i+1 to n do
{Compara cada clave con la actual clave_del_menor }
if A[j].clave < clave_del_menor then
begin
clave_del_menor := A[j].clave;
indice_del_menor := j;
103

end
intercambia(A[i] con A[indice_del_menor])
end
end

Este mtodo es O(n2) para todos los casos y es estable.


Seleccin
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#define N 10000
int a[N];
void seleccion()
{
int i, j, min, aux;
for(i = 1; i < N; i++)
{
min = i;
for(j = i + 1; j <= N; j++)
if(a[j] < a[min])
min = j;
aux = a[min];
a[min] = a[i];
a[i] = aux;
}
return;
}
void main(void)
{
srand(time(0));
for(int i = 0; i < N; i++)
a[i] = random(20000);
printf("Arreglo desordenado.\n");
for(i = 0; i < N; i++)
104

printf("%d\n", a[i]);
getch();
seleccion();
printf("Arreglo ordenado.\n");
for(i = 0; i < N; i++)
printf("%d\n", a[i]);
getch();
}
Tambin es otro de los mtodos ms sencillos y su principio de
funcionamiento es tambin el intercambio de objetos, pero en este caso
se intercambian objetos no necesariamente adyacentes. El mtodo
asume que los elementos a ordenar estn dispuestos sobre un arreglo.
En la primera pasada se halla el mnimo de los n elementos y se coloca
en la primera posicin del arreglo. En la segunda pasada se halla el
mnimo de los n-1 elementos restantes y se coloca en la segunda
posicin, y as sucesivamente, hasta que solo se considere una lista en
la que queden solo dos elementos a ordenar lo cual se logra
comparando los mismos en intercambindolos si fuera necesario.
Siguiendo esta estrategia, nos damos cuenta que tras i pasadas, los i
elementos menores estarn ordenados y ocuparan las posiciones
A[1] ... A[i] del arreglo.
El siguiente esquema ilustra el mtodo:

Figura : Pasos del algoritmo de mnimos sucesivos.

105

Una primera aproximacin al algoritmo sera la siguiente:


for i:=1 to n-1 do
Hallar el mnimo entre A[i], ... ,A[n];
if necesario then intercambiarlo con A[i];

Una versin ms completa sera la siguiente:


Begin
for i:=1 to n-1 do
begin
{hallar el mnimo entre A[i]...A[n] e intercambiarlo con A[i]}
indice_del_menor := i;
clave_del_menor := A[i].clave;
for j:=i+1 to n do
{Compara cada clave con la actual clave_del_menor }
if A[j].clave < clave_del_menor then
begin
clave_del_menor := A[j].clave;
indice_del_menor := j;
end
intercambia(A[i] con A[indice_del_menor])
end
end
Este mtodo es O(n2) para todos los casos y es estable.

Ordenacin por Shell sort


Es una mejora del mtodo de insercin directa, utilizado cuando el array tiene
un gran nmero de elementos. En este mtodo no se compara a cada
elemento con el de su izquierda, como en el de insercin, sino con el que est
a un cierto nmero de lugares (llamado salto) a su izquierda. Este salto es
constante, y su valor inicial es N/2 (siendo N el nmero de elementos, y siendo
106

divisin entera). Se van dando pasadas hasta que en una pasada no se


intercambie ningn elemento de sitio. Entonces el salto se reduce a la mitad, y
se vuelven a dar pasadas hasta que no se intercambie ningn elemento, y as
sucesivamente hasta que el salto vale 1.
Por ejemplo, lo pasos para ordenar el array {40,21,4,9,10,35} mediante el
mtodo de Shell seran:
Salto=3:
Primera pasada:
{9,21,4,40,10,35} <-- se intercambian el 40 y el 9.
{9,10,4,40,21,35} <-- se intercambian el 21 y el 10.
Salto=1:
Primera pasada:
{9,4,10,40,21,35} <-- se intercambian el 10 y el 4.
{9,4,10,21,40,35} <-- se intercambian el 40 y el 21.
{9,4,10,21,35,40} <-- se intercambian el 35 y el 40.
Segunda pasada:
{4,9,10,21,35,40} <-- se intercambian el 4 y el 9.
Con slo 6 intercambios se ha ordenado el array, cuando por insercin se
necesitaban muchos ms.
int array[N];
int salto,cambios,aux,i;
for(salto=N/2;salto!=0;salto/=2) // El salto va desde N/2 hasta 1.
{
for(cambios=1;cambios!=0; ) // Mientras se intercambie alg elemento:
{
cambios=0;
for(i=salto;i<N;i++)
// se da una pasada
if(array[i-salto]>array[i]) // y si est desordenados
{
aux=array[i];
// se reordenan
array[i]=array[i-salto];
array[i-salto]=aux;
Cambios++;
// y se cuenta como cambio.
107

}
}
}
Corrida de ordenacin shell:
Void ordenacin shell (double a[], int n)
{ int i, j, k, intervalo = n/2;
while (intervalo > 0)
{ for (i = intervalo ; i <= n, i++)
j = i intervalo;
while (j >=0)
{k = j + intervalo;
if (a [j] <=a [k]) j = - 1;
else { double temp}
}
}
}

Implementacion del algoritmo ShellSort


#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#define N 10000
int a[N];
void shellSort()
{
first = time(NULL);
int i, j, h, v;
for(h = 1; h <= N/9; h = 3*h+1);
for(; h > 0; h /= 3)
for(i = h+1; i <= N; i += 1)
{
v=a[i]; j=i;
while(j>h && a[j-h]>v)
a[j] = a[j-h]; j-=h;
a[j]=v;
}
second = time(NULL);
return difftime(second, first);
}
void main(void)
{

108

srand(time(0));
for(int i = 0; i < N; i++)
a[i] = random(20000);
printf("Arreglo desordenado.\n");
for(i = 0; i < N; i++)
printf("%d\n", a[i]);
getch();
shellSort();
printf("Arreglo ordenado.\n");
for(i = 0; i < N; i++)
printf("%d\n", a[i]);
getch();
}

Anlisis de Shellsort
El algoritmo funciona debido a que en el ltimo paso (con h = 1), una ordinaria
ordenacin por insercin se ejecuta para todo el arreglo. Pero como los datos
son previamente semiordenados, solo son necesarios algunos pasos del
Insertion sort. Con cunta exactitud realiza la ordenacin se mostrar a
continuacin. La secuencia de hs (denotada como h-secuencia) es solo una
de las posibles; de hecho, la eficiencia de Shellsort depende de qu hsecuencia es utilizada.

Ordenacin rpida (Quicksort)


Este mtodo se basa en la tctica "divide y vencers" (ver seccin divide y
vencers), que consiste en ir subdividiendo el array en arrays ms pequeos, y
ordenar stos. Para hacer esta divisin, se toma un valor del array como pivote,
y se mueven todos los elementos menores que este pivote a su izquierda, y los
mayores a su derecha. A continuacin se aplica el mismo mtodo a cada una
de las dos partes en las que queda dividido el array.
Normalmente se toma como pivote el primer elemento de array, y se realizan
dos bsquedas: una de izquierda a derecha, buscando un elemento mayor que
el pivote, y otra de derecha a izquierda, buscando un elemento menor que el
pivote. Cuando se han encontrado los dos, se intercambian, y se sigue
realizando la bsqueda hasta que las dos bsquedas se encuentran.Por
ejemplo, para dividir el array {21,40,4,9,10,35}, los pasos seran:

109

{21,40,4,9,10,35} <-- se toma como pivote el 21. La bsqueda de izquierda a


derecha encuantra el valor 40, mayor que pivote, y la bsqueda de derecha a
izquierda encuentra el valor 10, menor que el pivote. Se intercambian:
{21,10,4,9,40,35} <-- Si seguimos la bsqueda, la primera encuentra el valor
40, y la segunda el valor 9, pero ya se han cruzado, as que paramos. Para
terminar la divisin, se coloca el pivote en su lugar (en el nmero encontrado
por la segunda bsqueda, el 9, quedando:
{9,10,4,21,40,35} <-- Ahora tenemos dividido el array en dos arrays ms
pequeos: el {9,10,4} y el {40,35}, y se repetira el mismo proceso.
La implementacin es claramente recursiva (ver recursividad), y suponiendo el
pivote el primer elemento del array, el programa sera:
#include <stdio.h>
void ordenar(int *,int,int);
void main()
{
// Dar valores al array
ordenar(array,0,N-1); // Para llamar a la funcin
}
void ordenar(int *array,int desde,int hasta)
{
int i,d,aux; // i realiza la bsqueda de izquierda a derecha
// y j realiza la bsqueda de derecha a izquierda.
if(desde>=hasta)
return;
for(i=desde+1,d=hasta; ; ) // Valores iniciales de la bsqueda.
{
for( ;i<=hasta && array[i]<=array[desde];i++); // Primera bsqueda
for( ;d>=0 && array[d]>=array[desde];d--); // segunda bsqueda
if(i<d)
// si no se han cruzado, intercambiar
{
aux=array[i];
array[i]=array[d];
array[d]=aux;
}
else
// si se han cruzado, salir del bucle
break;
}
110

if(d==desde-1) // Si la segunda bsqueda se sale del array es que el


d=desde; // pivote es el elemento ms pequeo: se cambia con l mismo
aux=array[d]; // Colocar el pivote en su posicin
array[d]=array[desde];
array[desde]=aux;
ordenar(array,desde,d-1); // Ordenar el primer subarray.
ordenar(array,d+1,hasta); // Ordenar el segundo subarray.
}
En C hay una funcin que realiza esta ordenacin sin tener que implementarla,
llamada qsort (incluida en stdlib.h):
qsort(nombre_array,nmero,tamao,funcin);
donde nombre_array es el nombre del array a ordenar, nmero es el nmero de
elementos del array, tamao indica el tamao en bytes de cada elemento y
funcin es un puntero a una funcin que hay que implementar, que recibe dos
elementos y devuelve 0 si son iguales, algo menor que 0 si el primero es menor
que el segundo, y algo mayor que 0 si el segundo es menor que el primero. Por
ejemplo, el programa de antes sera:
#include <stdio.h>
#include <stdlib.h>
int funcion(const void *,const void *);
void main()
{
// Dar valores al array
qsort(array,N,sizeof(array[0]),funcion);
}
int funcion(const void *a,const void *b)
{
if(*(int *)a<*(int *)b)
return(-1);
else if(*(int *)a>*(int *)b)
return(1);
else
return(0);
}

111

Claramente, es mucho ms cmodo usar qsort que implementar toda la


funcin, pero hay que tener mucho cuidado con el manejo de los punteros en la
funcin, sobre todo si se est trabajando con estructuras.

Algoritmo codificado en C que ordena una lista dinmica con el


Quicksort.
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<graphics.h>
typedef struct NODO nodo;
struct NODO
{
int valor;
int p;
nodo *ant, *sig;
};
void imprime(void);
void crea_lista(nodo **lista, int N);
void libera_lista(nodo **lista);
nodo *partition(nodo *infer, nodo *sup);
void quick(nodo *ini, nodo *fin);
nodo *aux = NULL;
int comp = 0, inter = 0, n;
void main(void)
{
nodo *lista = NULL;
clrscr();
cprintf(" Cuantos nmeros: ");
scanf("%d", &n);
crea_lista(&lista, n);
aux = lista;
imprime();
112

quick(lista,lista -> ant);


imprime();
printf("\n");
printf("\nComparaciones: %d Intercambios: %d", comp, inter);
libera_lista(&lista);
getch();
clrscr();
}
void crea_lista(nodo **lista, int N)
{
int i;
nodo *tmp = NULL;
randomize();
for(i = 0; i < N; i++){
tmp = (nodo *)malloc(sizeof(nodo));
tmp -> p = i;
tmp -> valor = random(100);
if(!(*lista))
*lista = tmp;
else
{
if(i < 2)
{
(*lista) -> ant = tmp;
tmp -> sig = *lista;
}
else
{
(*lista) -> sig -> ant = tmp;
tmp -> sig = (*lista) -> sig;
}
}
(*lista) -> sig = tmp;
tmp -> ant = *lista;
*lista = tmp;
}
*lista = (*lista) -> sig;
}
void libera_lista(nodo **lista)
{
nodo *tmp = NULL;
while((*lista) -> sig)
{
tmp = (*lista) -> sig;
(*lista) -> sig = tmp -> sig;
tmp -> sig -> ant = *lista;

113

tmp -> sig = NULL;


tmp -> ant = NULL;
free(tmp);
tmp = NULL;
}
free(*lista);
}
nodo *partition(nodo *infer, nodo *sup)
{
int i, tmp, a;
nodo *inf;
inf = infer;
a = inf -> valor;
while(inf -> p < sup -> p)
{
while(inf -> valor <= a && inf -> p < sup -> p)
{
inf = inf -> sig;
comp++;
}
while(sup -> valor > a && sup -> p > infer -> p)
{
sup = sup -> ant;
comp++;
}
if(inf -> p < sup -> p)
{
tmp = inf -> valor;
inf -> valor = sup -> valor;
sup -> valor = tmp;
inter++;
}
}
if(infer != sup)
{
infer -> valor = sup -> valor;
sup -> valor = a;
inter++;
}
return sup;
}
void quick(nodo *ini, nodo *fin)
{
nodo *f;
if((f -> p == 0 && fin -> p == f -> sig -> p) && inter);

114

else if(fin -> p > ini -> p)


{
f = partition(ini, fin);
if(f != ini)
quick(ini, f -> ant);
if(f != fin)
quick(f -> sig, fin);
}
}

void imprime()
{
int i;
for(i = 0; i < n; i++)
{
printf("%d ", aux -> valor);
aux = aux -> sig;
}
printf("\n");
getch();
}
RAPIDORECURSIVO(A, N)
{El algoritmo ordena los elementos del arreglo utilizando el mtodo rpido, de
manera recursiva. A es un arreglo de N elementos}
1. Llamar al algoritmo REDUCERECURCIVO CON 1 y N
Observe el algoritmo rpido recursivo requiere para su funcionamiento de otro
algoritmo, que presentamos a continuacin.

REDUCERECURCIVO (INI, FIN)


{INI y FIN representan las posiciones del extremo izquierdo y derecho
respectivamente, del conjunto de elementos a ordenar}
{IZQ, DER, POS y AUX son variables de tipo entero. BAND es una variable de
tipo booleano}

115

1. Hacer IZQ = INI, DER=IN, POS=INI y BAND=VERDADERO


2. repetir mientras (BAND = VERDADERO)
Hacer BAND=FALSO
2.1 Repetir mientras (A[POS] A[DER]) y (POS DER)
Hacer BAND =FALSO
2.2 {Fin del ciclo del paso 2.1}
2.3 si POS DER entonces
Hacer AUX= A[POS], A[POS] = A [DER],
A [DER] = AUX y POS =DER
2.3.1 Repetir mientras (A [POS] A[IZQ] y (POS IZQ)
Hacer IZQ=IZQ+1
2.3.2 {Fin del paso 2.3.1}
2.3.3 Si POS IZQ entonces
Hacer BAND = VERDADERO, AUX= A[POS],
A[POS] =A[IZQ] , A[IZQ] =AUX, POS =IZQ
2.3.4 {Fin del condicional del paso 2.3.3}
2.4 {Fin del condicional del paso 2.3}
3. {Fin del ciclo del paso 2}
4. Si (POS-1) > INI entonces
Regresar a REDUCERECURCIVO con INI y (POS-1) {Llamada recursiva}
5. {Fin del condicional del paso 4}
6. Si FIN > (POS+1) entonces
Regresar a REDUCERECURCIVO con (POS+1) y FIN {Llamada recursiva}
7. {Fin del condicional del paso 6}
Aun cuando el algoritmo del QUICKSORFT presentado resulte claro, es posible
aumentar su velocidad de ejecucin eliminando las llamadas recursivas. La
recursin es un elemento muy poderoso, pero la eficiencia de ejecucin es un
factor muy importante en un proceso de ordenamiento que es necesario cuidar
y administrar muy bien. Estas llamadas recursivas pueden sustituirse utilizando
pilas, en lugar de interactividad.
Los ndices del primer conjunto quedaron ordenados en la primera posicin de
PILAMENOR y PILAMAYOR respectivamente. La posicin del extremo derecho
del primer conjunto (1) en PILAMENOR y la posicin del extremo derecho del
mismo conjunto (2) en PILAMAYOR. Las posiciones de los extremos izquierdos
y derecho del segundo conjunto (4 y 8) fueron almacenados en la cima de
PILAMENOR y PILAMAYOR, respectivamente.
RAPIDOITERATIVO (A, N)

116

{El algoritmo ordena los elementos de u arreglo utilizando el mtodo rpido, de


manera iterativa. A es un arreglo de N elementos}
{TOP, INI, FIN y POS son variables de tipo entero. PILAMENOR Y
PILAMAYOR son arreglos unidimensionales}
1. Hacer TOP = 1, PILAMENOR [TOP], FIN = 1 y PILAMAYOR [TOP] = N
2. Repetir mientras (TOP>0)
Hacer INI= PILAMENOR [TOP], FIN = PILAMAYOR [TOP] y
TOP= TOP-1
Llamar al algoritmo REDUCEITERATIVO con INI, FIN y POS.
2.1 Si INI < (POS-1) entonces
Hacer TOP= TOP+1, PILAMAYOR [TOP] = INI y
PILAMAYOR [TOP] = POS-1
2.2 {Fin del condicional del paso 2.1}
2.3 Si FIN > (POS+1) entonces
Hacer TOP = TOP+1, PILAMENOR [TOP] = POS + 1
y PILAMAYOR [TOP] =
FIN
2.4 {Fin de la condicional}
3. {Fin del ciclo del paso 2}
Ntese que el algoritmo rapidoiterativo necesita para su funcionamiento de
otro algoritmo, que se presenta a continuacin.

REDUCEITERATIVO (INI. FIN, POS)


{INI y FIN representan las posiciones de los extremos izquierdo y derecho
respectivamente, del conjunto de elementos a evaluar. POS es una variable
donde se almacenara el resultado de este algoritmo}
{IZQ, DER y AUX son variables de tipo entero. BAND variable de tipo booleano}
1. Hacer IZQ = INI, DER = FIN, POS = INI y BAND = VERDADERO
2. Repetir mientras (BAND = VERDADERO)
Hacer DER = DER-1
2.1 mientras (A[POS] A[DER]) y (POS DER)
Hacer DER = DER-1
2.2 {Fin del ciclo 2.1}
2.3 si POS = DER
entonces
Hacer BAND = FALSO
Si no
Hacer AUX
A[POS], A[POS] = A[DER],
A[DER] = AUX y POS = DER
2.3.1 Repetir mientras (A[POS] A [IZQ]) y (POS IZQ)
2.3.2 {Fin del ciclo del paso 3.2.1}
2.3.3 Si POS = IZQ

117

entonces
Hacer BAND =
FALSO
Si no
Hacer AUX =
A[POS], A[POS] =
A [IZQ]
A[IZQ]
= AUX y POS =
IZQ
2.3.4 {Fin del condicional del paso 2.3.3}
2.4 {Fin del condicional del paso 2.3}
2. {Fin del ciclo del paso 2}.
SECUENCIALCONLISTAS (P,X)
{Este algoritmo busca secuencialmente al elemento X en la lista cuyo nodo
inicial esta apuntado por P}
{Q es una variable de tipo puntero. BANDERA es una variable de tipo
booleano}
1. Hacer Q = P y BANDERA
FALSO
2. Repetir mientras (Q NIL) y (BANDERA = FALSO) y (X Q^ .INFO)
2.1 SI Q^.INFO = X
Entonces
Hacer BANDERAS = VERDADERO
Si no
Hacer Q =

Q^.LIGA

2.2 {Fin del condicional del paso 2.1}


3. {Fin del ciclo del paso 2}
4. Si BANDERA = VERDADERO
Entonces
Escribir El elemento esta en la lista
Si no
Escribir El elemento no esta en la lista
5. {Fin del condicional del paso 4}

Si

la

lista

estuviera

ordenada,

debera

modificarse

el

algoritmo

SECUENCIALCONLISTAS, incluyendo una condicin similar a la que se


escribi en el algoritmo SECUENCIALORDENADO,

de tal manera que

permita disminuir el nmero de comparaciones.


El nmero de comparaciones determina la complejidad de los mtodos de No
es propiamente un mtodo de ordenacin, consiste en la unin de dos arrays
ordenados de modo que la unin est tambin ordenada. Para ello, basta con
recorrer los arrays de izquierda a derecha e ir cogiendo el menor de los dos

118

elementos, de forma que slo aumenta el contador del array del que sale el
elemento siguiente para el array-suma. Si quisiramos sumar los arrays {1,2,4}
y {3,5,6}, los pasos seran:
bsqueda. Analice la complejidad del mtodo secuencial.
Ordenacin por Intercalacin:
Inicialmente: i1=0, i2=0, is=0.
Primer elemento: mnimo entre 1 y 3 = 1. Suma={1}. i1=1, i2=0, is=1.
Segundo elemento: mnimo entre 2 y 3 = 2. Suma={1,2}. i1=2, i2=0, is=2.
Tercer elemento: mnimo entre 4 y 3 = 3. Suma={1,2,3}. i1=2, i2=1, is=3.
Cuarto elemento: mnimo entre 4 y 5 = 4. Suma={1,2,3,4}. i1=3, i2=1, is=4.
Como no quedan elementos del primer array, basta con poner los elementos
que quedan del segundo array en la suma:
Suma={1,2,3,4}+{5,6}={1,2,3,4,5,6}
int i1,i2,is;
int array1[N1],array2[N2],suma[N1+N2];
for(i1=i2=is=0;i1<N1 && i2<N2;is++) // Mientras no se me acabe ni array1 ni
array2:
{
if(array1[i1]<array2[i2]) // Si el elemento de array1 es menor:
{
suma[is]=array1[i1]; // se utiliza el de array1.
i1++;
}
else
// Pero si el elemento de array2 es menor:
{
suma[is]=array2[i2]; // se utiliza el de array2.
i2++;
}
}
for( ;i1<N1;i1++,is++) // Aadir los elementos de array1 (si quedan).
suma[is]=array1[i1];
for( ;i2<N2;i2++,is++) // Aadir los elementos de array2 (si quedan).
suma[is]=array2[i2];

Caractersticas

119

Es un algoritmo recursivo con un numero de comparaciones minimo. El tiempo


de ejecucin promedio es O(N log(N)).
.Su desventaja es que trabaja sobre un array auxiliar lo cual tiene dos
consecuencias: uso de memoria extra y trabajo extra consumido en las copias
entre arreglos (aunque es un trabajo de tiempo lineal).
.Es una aplicacin clsica de la estrategia para resolucin de algoritmos divide
y venceras. Esta estrategia plantea el hecho de que un problema puede ser
dividido en varios subproblemas y una vez resuelto estos se puede proceder a
unir las soluciones para formar la solucin del problema general. La solucion de
los subproblemas mas pequeos se realiza de la misma manera: es decir se
van resolviendo problemas cada vez mas pequeos (hasta encontrarnos con
un caso base: problema no divisible con solucion tribial).
En cada recurcion se toma un array de elementos desordenados. Se lo divide
en dos mitades, se aplica la recurcion en cada una de estas y luego (dado que
al finalizar estas recurciones tenemos las dos mitades ordenadas) se intercalan
ambas para obtener el array ordenado.
Intercalar: Es la operacin que le da el nombre a este algoritmo. La
intercalacin toma dos secuencias (arrays) de elementos y apartir de estas
construye una tercera secuencia que contiene todos los elementos de estas en
orden
Implementacin de la intercalacin. Sean dos arrays ordenados A y B (cuyas
longitudes pueden ser distintas). Sea el array

tal que |C|>=|A|+|B| (tiene

capacidad para almacenar todos los elementos de ambas listas A y B). Sean
los contadores ap, bp y cp con valor inicial 0 (se ponen al inicio de sus arreglos
respectivos). Lo que hace intercalar es copiar el menor de A[ap] y B[bp] en
C[cp] y avanzar los contadores apropiados. Cuando se agota cualquier lista de
entrada (A o B), los datos que quedan en la otra lista se copian en C.
Esperemos que al leer el codigo, el lector entienda los detalles menores tanto
de la rutina recursiva del algoritmo de recursin como de la rutina intercala ().
Void ord intercalacin (Dato * A, Dato * Temp., int izq, int der); /*funcion
recursiva! A es el array de datos. Tmp debe ser un array del tamao mayor o

120

igual a A. Izq y der son los extremos del subarreglo sobre el cual trabajaremos
en esta recursin. * /
Void intercalar ( Dato * A, Dato * Temp. Int izq, int centro, int der );
/* lo que hace esta rutina es intercalar las particiones [A[izq*, ..., A[centro_1] ] y
[ A [centro],..., A[der] ] (que deberan estar ya ordenadas) en el subarray.
[ tmp[izq], ..., tmp[der] ] y luego copiar los elementos nuevamente a A, en el
subarray [ A [izq], ..., A[der] ] * / void

mergesort (dato * A, int N)


{
Dato

temp = crear (N) ;

/ creamos un array auxiliary del mismo tamao

que A */
Ord_intercalacin (A,Temp.,O,N_1);
}
void
ord_intercalacin (Dato * A, Dato Temp., int izq, int der)
{
if (izq < der)
/* Este if comprueba el caso base que es cuando la participacion pasada no
tiene elementos. */
{
/* Dividimos a la mitad el subarray [A[izq],...,A[der] ]

*/

int centro = (izq + der) / 2;


/* Aplicamos la recurcion en ambas mitades */
ord_intercalacin (A, tmp, izq, centro);
ord_intercalacin (A, tmp, centro+1, der);
/* a este punto ambas mitades deberan estar ordenados por lo que las
intercalamos en una secuencia ordenada.*/
Intercalar (A, tmp, izq, centro+1, der);
}
}
121

void
Intercalar(Dato * tmp, int izq, int centro, int der)
{
/*Participacion seran [izq,,centro -1] y
[Centro, .der]*/
/*Contadores para la primera mitad la segunda y paga la
Intercalacin respectivamente.*/
int ap = izq, bp=centro, cp=izq;
while ((ap < centro)&&(bp<=der))
{
if (A[ap]<=A[bp])
{
tmp[cp]=A[ap];
ap++;
}
else
{
tmp[cp]=A[bp];
bp++;
}
cp++;
}
/*Terminamos de intercalary, ahora metemos los elementos de la lista que aun
no ha terminado de ser procesada.*/
while(ap<centro)
{
tmp[cp]=A[ap];
cp++;
ap++;
}
while(bp<=der)
{
tmp[cp]=A[bp];
cp++;
122

bp++;
}
/*Ahora que tenemos la intercalacion finalizada en tmp, la pasamos a A */
for(ap=izq; ap<=der; ap++)
A[ap]=tmp[ap];
}
Observar como la funcion principal mergesort() solamente es un manejador de
la funcion ord_intercalacin() en la cual se realiza todo el trabajo
recursivamente.
Si bien el algoritmo puede parecer largo es mas facil (desde nuestro punto de
vista) entenderlo que otros algoritmos mas cortos pero mas complejos como
por ejemplo la ordenacin de shell. La unica parte difcil es entender como
funciona la recurcion ya que el algoritmo intercalar es bastante facil.

Fusin: Consta de dos partes, una parte de intercalacin de listas y otra de


divide y vencers.
- Primera parte: Cmo intercalar dos listas ordenadas en una sola lista
ordenada de forma eficiente?
Suponemos

que

se

tienen

estas

dos

listas

de

enteros

ordenadas

ascendentemente:
lista 1: 1 -> 3 -> 5 -> 6 -> 8 -> 9
lista 2: 0 -> 2 -> 6 -> 7 -> 10
Tras mezclarlas queda:
lista: 0 -> 1 -> 2 -> 3 -> 5 -> 6 -> 6 -> 7 -> 8 -> 9 -> 10
Esto se puede realizar mediante un nico recorrido de cada lista, mediante dos
punteros que recorren cada una. En el ejemplo anterior se insertan en este
orden -salvo los dos 6 que puede variar segn la implementacin-: 0 (lista 2), el
123

1 (lista 1), el 2 (lista 2), el 3, 5 y 6 (lista 1), el 6 y 7 (lista 2), el 8 y 9 (lista 1), y
por llegar al final de la lista 1, se introduce directamente todo lo que quede de
la lista 2, que es el 10.
En la siguiente implementacin no se crea una nueva lista realmente, slo se
modifican los enlaces destruyendo las dos listas y fusionndolas en una sola.
Se emplea un centinela que apunta a s mismo y que contiene como clave el
valor ms grande posible. El ltimo elemento de cada lista apuntar al
centinela, incluso si la lista est vaca.

struct lista
{
int clave;
struct lista *sig;
};
struct lista *centinela;
centinela = (struct lista *) malloc(sizeof(struct lista));
centinela->sig = centinela;
centinela->clave = INT_MAX;
...
struct lista *fusion(struct lista *l1, struct lista *l2)
{
struct lista *inic, *c;
if (l1->clave < l2->clave) { inic = l1; l1 = l1->sig; }
else { inic = l2; l2 = l2->sig; }
c = inic;
while (l1 != centinela && l2 != centinela) {
if (l1->clave < l2->clave) {
c->sig = l1; l1 = l1->sig;
}
else {
c->sig = l2; l2 = l2->sig;
}
c = c->sig;
}
if (l1 != centinela) c->sig = l1;
else if (l2 != centinela) c->sig = l2;
return inic;
}

124

- Segunda parte: divide y vencers. Se separa la lista original en dos trozos del
mismo tamao (salvo listas de longitud impar) que se ordenan recursivamente,
y una vez ordenados se fusionan obteniendo una lista ordenada. Como todo
algoritmo basado en divide y vencers tiene un caso base y un caso recursivo.

* Caso base: cuando la lista tiene 1 0 elementos (0 se da si se trata de


ordenar una lista vaca). Se devuelve la lista tal cual est.
* Caso recursivo: cuando la longitud de la lista es de al menos 2 elementos.
Se divide la lista en dos trozos del mismo tamao que se ordenan
recursivamente. Una vez ordenado cada trozo, se fusionan y se devuelve la
lista resultante.
El esquema es el siguiente:

Ordenar(lista L)
inicio
si tamao de L es 1 o 0 entonces
Devolver L
si tamao de L es >= 2 entonces
Separar L en dos trozos: L1 y L2.
L1 = Ordenar(L1)
L2 = Ordenar(L2)
L = Fusionar (L1, L2)
Devolver L
fin
El algoritmo funciona y termina porque llega un momento en el que se obtienen
listas de 2 3 elementos que se dividen en dos listas de un elemento (1+1=2) y
en dos listas de uno y dos elementos (1+2=3, la lista de 2 elementos se volver

125

a dividir), respectivamente. Por tanto se vuelve siempre de la recursin con


listas ordenadas (pues tienen a lo sumo un elemento) que hacen que el
algoritmo de fusin reciba siempre listas ordenadas.
Se incluye un ejemplo explicativo donde cada sublista lleva una etiqueta
identificativa.

Dada: 3 -> 2 -> 1 -> 6 -> 9 -> 0 -> 7 -> 4 -> 3 -> 8 (lista original)

se divide en:
3 -> 2 -> 1 -> 6 -> 9 (lista 1)
0 -> 7 -> 4 -> 3 -> 8 (lista 2)

se ordena recursivamente cada lista:

3 -> 2 -> 1 -> 6 -> 9 (lista 1)

se divide en:

3 -> 2 -> 1 (lista 11)


6 -> 9 (lista 12)
se ordena recursivamente cada lista:
3 -> 2 -> 1 (lista 11)
se divide en:
3 -> 2 (lista 111)
1 (lista 112)
se ordena recursivamente cada lista:
3 -> 2 (lista 111)
se divide en:
3 (lista 1111, que no se divide, caso base). Se devuelve 3
2 (lista 1112, que no se divide, caso base). Se devuelve 2
se fusionan 1111-1112 y queda:
2 -> 3. Se devuelve 2 -> 3
1 (lista 112)
1 (lista 1121, que no se divide, caso base). Se devuelve 1
126

se fusionan 111-112 y queda:


1 -> 2 -> 3 (lista 11). Se devuelve 1 -> 2 -> 3
6 -> 9 (lista 12)
se divide en:
6 (lista 121, que no se divide, caso base). Se devuelve 6
9 (lista 122, que no se divide, caso base). Se devuelve 9
se fusionan 121-122 y queda:
6 -> 9 (lista 12). Se devuelve 6 -> 9
se fusionan 11-12 y queda:
1 -> 2 -> 3 -> 6 -> 9. Se devuelve 1 -> 2 -> 3 -> 6 -> 9
0 -> 7 -> 4 -> 3 -> 8 (lista 2)
... tras repetir el mismo procedimiento se devuelve 0 -> 3 -> 4 -> 7 -> 8
se fusionan 1-2 y queda:
0 -> 1 -> 2 -> 3 -> 3 -> 4 -> 6 -> 7 -> 8 -> 9, que se devuelve y se termina.

La implementacin propuesta emplea un centinela sobre la lista inicial que


apunte hacia s mismo y que adems contiene el mximo valor de un entero.
La lista dispone de cabecera y centinela, pero obsrvese como se elimina
durante la ordenacin.
#include <stdlib.h>
#include <limits.h>
struct lista
{
int clave;
struct lista *sig;
};
/* lista con cabecera y centinela */
struct listacc
{
struct lista *cabecera,
*centinela;
};
/* centinela declarado como variable global */
127

struct lista *centinela;


/* fusiona dos listas */
struct lista *fusion(struct lista *l1, struct lista *l2)
{
struct lista *inic, *c;
if (l1->clave < l2->clave) { inic = l1; l1 = l1->sig; }
else { inic = l2; l2 = l2->sig; }
c = inic;
while (l1 != centinela && l2 != centinela) {
if (l1->clave < l2->clave) {
c->sig = l1; l1 = l1->sig;
}
else {
c->sig = l2; l2 = l2->sig;
}
c = c->sig;
}
if (l1 != centinela) c->sig = l1;
else if (l2 != centinela) c->sig = l2;
return inic;
}
/* algoritmo de ordenacin por fusin mediante divide y vencers */
struct lista *ordenfusion(struct lista *l)
{
struct lista *l1, *l2, *parte1, *parte2;
/* caso base: 1 0 elementos */
if (l->sig == centinela) return l;
/* caso recursivo */
/* avanza hasta la mitad de la lista */
l1 = l; l2 = l1->sig->sig;
while (l2 != centinela) {
l1 = l1->sig;
l2 = l2->sig->sig;
}
/* la parte en dos */
l2 = l1->sig;
l1->sig = centinela;
/* ordena recursivamente cada parte */
parte1 = ordenfusion(l);
parte2 = ordenfusion(l2);
/* mezcla y devuelve la lista mezclada */
l = fusion(parte1, parte2);

128

return l;
}
/* operaciones de lista */
void crearLCC(struct listacc *LCC)
{
LCC->cabecera = (struct lista *) malloc(sizeof(struct lista));
LCC->centinela = (struct lista *) malloc(sizeof(struct lista));
LCC->cabecera->sig = LCC->centinela;
LCC->centinela->sig = LCC->centinela;
}
/* inserta un elemento al comienzo de la lista */
void insertarPrimero(struct listacc LCC, int elem)
{
struct lista *nuevo;
nuevo = (struct lista *) malloc(sizeof(struct lista));
nuevo->clave = elem;
nuevo->sig = LCC.cabecera->sig;
LCC.cabecera->sig = nuevo;
}
int main(void)
{
struct listacc LCC;
crearLCC(&LCC);
centinela = LCC.centinela;
centinela->clave = INT_MAX;
insertarPrimero(LCC, 8);
insertarPrimero(LCC, 3);
insertarPrimero(LCC, 4);
insertarPrimero(LCC, 7);
insertarPrimero(LCC, 0);
insertarPrimero(LCC, 9);
insertarPrimero(LCC, 6);
insertarPrimero(LCC, 1);
insertarPrimero(LCC, 2);
insertarPrimero(LCC, 3);
LCC.cabecera = ordenfusion(LCC.cabecera->sig);
return 0;
}
Ordenacin por montculos (Heapsort)
Recordemos que un montculo Max es un arbol binario completo cuyos elementos estan
ordenados del siguiente modo: para cada subrbol se cumple que la raiz es mayor que
129

ambos hijos. Si el montculo fuera Min, la raiz de cada subrbol tiene que cumplir con
ser menor que sus hijos.
Recordamos que, si bien un montculo se define como un arbol, para
representar este se utiliza un array de datos, en el que se acceden a padres e
hijos utilizando las siguientes transformaciones sobre sus indices. Si el
montculo esta almacenado en el array A, el padre de A[i] es A[i/2] (truncando
hacia abajo), el hijo izquierdo de A[i] es A[2*i] y el hijo derecho de A[i]es
A[2*i+1].

Caractersticas:

Es un algoritmo que se construye utilizando las propiedades de los


montculos binarios.

El orden de ejecucin para el peor caso es O(N.log(N)), siendo N el


tamao de la entrada.

Aunque tericamente es ms rapido que los algoritmos de ordenacin


vistos hasta aqu, en la prctica es mas lento que el algoritmo de
ordenacin de shell utilizando la secuencia de incrementos de
Sedgewick.

Al insertar o eliminar elementos de un montculo, hay que cuidar de no destruir


la propiedad de orden del montculo. Lo que se hace generalmente es construir
rutinas de filtrado (que puede ser ascendentes o descendentes) que tomen un
elemento del montculo (el elemento que viola la propiedad de orden) y lo
muevan verticalmente por el arbol hasta encontrar una posicin en la cual se
respete el orden de los elementos del montculo.
Tanto la insercin como la eliminacin (Eliminar_min O Eliminar_max)
Segn sea un montculo Min o Max respectivamente), de un elemento en un
montculo se realizan en un tiempo O(log(N)), peor caso(y eso se debe al orden
de sus elementos y a la caracterstica de arbol binario completo).
Estrategia general del algoritmo.
A grandes razgos el algoritmo de ordenacin por montculos consiste en meter
todos los elementos des array de datos en un montculo

130

MAX, y luego realizar N veces


Eliminar_max()
De ese modo, la secuencia de elementos eliminados nos sera entregada en
orden decreciente.
Existen dos razones por las que la estrategia general debe ser refinada: el uso
de un tad auxiliar (montculo binario)lo cual podria implicar demasiado codigo
para un simple algoritmo de ordenacin, y la necesidad de memoria adicional
para el montculo y para una posible copia del array. Estas cosas tambien
implican un gasto en velocidad.
Lo que se hace es reducir el codigo al mximo reducindose lo mas posible la
abstraccin a un montculo (insertando cada elemento).Lo que hace es, a cada
elemento de la mitad superior del array (posiciones, 0,1,...,N/2) se le aplica un
filtrado descendente (se baja el elemento por el arbol binario hasta que
tengan dos hijos que cumplan con el orden del montculo. Esto bastara para
hacer que el array cumpla con ser un montculo binario.
Notamos tambien que al hacer un eliminar_max() se elimina el primer elemento
del array, se libera un lugar a lo ultimo de este. Podemos usar esto para no
tener que hacer una copia del array para meter las eliminaciones sucesivas. Lo
que hacemos es meter la salida eliminar_max(), el array quede ordenado de
menor a mayor.
Para ahorrar codigo queremos lograr insertar y eliminar_max() con una sola
rutina de filtrado descendente. Ya explicamos como hacer que el array de datos
A preserve el orden de los elementos de un montculo. Lo que hacemos para
ordenarlos usando solo la rutina de filtrado descendente es un eliminar_max()
intercambiando el primer elemento hasta que se respete el orden Max. Quizs
esto quede claro mirando el codigo.

Bsqueda secuencial

Consiste en recorrer y examinar cada uno de los elementos del array hasta
encontrar el o los elementos buscados, o hasta que se han mirado todos los
elementos del array.
131

for(i=j=0;i<N;i++)
if(array[i]==elemento)
{
solucion[j]=i;
j++;
}
Este algoritmo se puede optimizar cuando el array est ordenado, en cuyo caso
la condicin de salida cambiara a:
for(i=j=0;array[i]<=elemento;i++)
o cuando slo interesa conocer la primera ocurrencia del elemento en el array:
for(i=0;i<N;i++)
if(array[i]==elemento)
break;
En este ltimo caso, cuando slo interesa la primera posicin, se puede utilizar
un centinela, esto es, dar a la posicin siguiente al ltimo elemento de array el
valor del elemento, para estar seguro de que se encuentra el elemento, y no
tener que comprobar a cada paso si seguimos buscando dentro de los lmites
del array:
array[N]=elemento;
for(i=0;;i++)
if(array[i]==elemento)
break;
Si al acabar el bucle, i vale N es que no se encontraba el elemento. El nmero
medio de comparaciones que hay que hacer antes de encontrar el elemento
buscado es de (N+1)/2.

132

Podemos tratar un poco sobre la bsqueda secuencial en arreglos,y luego en


listas enlazadas. Cuando se habla de bsqueda en arreglo debe distinguirse
entre arreglos desordenados y arreglos ordenados. La bsqueda secuencial en
arreglos desordenados consiste, bsicamente, en recorrer el arrelgo de
izquierda a derecha hasta que se encuentre el elemento buscado o se termine
el arreglo, lo que ocurra primero. Normalmente cuando una funcion de
bsqueda concluye con xito, interesa conocer en que pocision fue hayado el
elemento buscado. Esta idea puede generalizarse para todos los metodos de
bsqueda.
El Algoritmo de bsqueda secuencial en arreglo desordenado es el que se
explica a continuacin:
Algoritmo: Secuencial desordenado:
Secuencialdesordenado(V,N,X)
{Este algoritmo busca secuencialmente el elemento X en el arreglo
desordenado V, de N componente}
{I es una variable de tipo entero BANDERA es una variable de tipo booleano}
1.Hacer I1 y BanderaFalso
2.Repetir mientras(I<=N)

ANLISIS DE LA BUSQUEDA SECUENCIAL


Para analizar la complejidad de la bsqueda secuencial debe establecerse el
caso ms favorable y el ms desfavorable que puedan presentarse. Al buscar
un elemento en un arreglo desordenado de N componentes, puede suceder
que ese valor no se encuentre, por lo tanto se harn N comparaciones al
recorrer todo el arreglo. Por otra parte, si el elemento est en el arreglo, puede
estar en la primera posicin, en la ltima o en alguna intermedia. Si es el
primero se har una comparacin, si es el ltimo, se harn N comparaciones, y
si est en la posicin i (1<i<N) se harn i comparaciones.
Ahora bien, el nmero de comparaciones a realizar trabajando con arreglos
ordenados, ser igual que para arreglos desordenados cuando el elemento

133

se encuentre en el arreglo. En el caso contrario, el nmero de


comparaciones disminuir siempre que el valor buscado est comprendido
entre el primero y el ltimo elemento del arreglo.
Respecto a listas ligadas, el nmero de comparaciones es el mismo que
para arreglos. El orden entre los nodos de la lista influye en el nmero de
comparaciones de igual manera que el orden entre los componentes del
arreglo.

Bsqueda Binaria.
La bsqueda binaria es el mtodo ms eficiente para encontrar
elementos en un arreglo ordenado. El proceso comienza comparando el
elemento central del arreglo con el valor buscado. Si ambos coinciden
finaliza la bsqueda. Si no ocurre as, el elemento buscado ser mayor o
menor en sentido estricto que el central del arreglo. Si el elemento
buscado es mayor se procede a hacer Bsqueda binaria en el subarray
superior, si el elemento buscado es menor que el contenido de la casilla
central, se debe cambiar el segmento a considerar al segmento que est
a la izquierda de tal sitio central.
ALGORITMO:
{Este algoritmo busca el elemento X en el arreglo ordenado V de N
componentes}
{izq, cen, der, var, tipo entero. Bandera. Booleano}
1. Hacer izq1.DerN y BanderaFalso.
2. Repetir mientras(izq<=Der) y (Bandera. Falso)
Hacer CENPARTE ENTERA((izq+Der)/2)
2.1 Si X=V[CEN]
Entonces
Hacer banderaVERDADERO
Sino{Redefinir intervalo de busqueda}
2.1.1 Si X>V[CEN]
134

entonces
Hacer izqCEN+1
Si no
Hacer DERCEN-1
2.1.2{Fin del condicional del paso 2.1.1}
2.2 {Fin del condicional del paso 2.1}
3{Fin del ciclo del paso 2}
4 Si BANDERA=VERDADERO
entonces
Escribir El elemento esta en la posicin CEN
Sino
Escribir El elemento no esta en el arreglo
Fin del condicional del paso 4.

TABLAS HASH

INTRODUCCIN.
Una aproximacin a la bsqueda radicalmente diferente a las anteriores
consiste en proceder, no por comparaciones entre valores clave, sino
encontrando alguna funcin h(k) que nos d directamente la localizacin
de la clave k en la tabla.
La primera pregunta que podemos hacernos es si es fcil encontrar tales
funciones h. La respuesta es, en principio, bastante pesimista, puesto
que si tomamos como situacion ideal el que tal funcin d siempre
localizaciones distintas a claves distintas y pensamos p.ej. en una tabla
de tamao 40 en donde queremos direccionar 30 claves, nos
encontramos con que hay 4030 = 1.15 * 1048 posibles funciones del
conjunto de claves en la tabla, y slo 40*39*11 = 40!/10! = 2.25 * 10 41 de
ellas no generan localizaciones duplicadas. En otras palabras, slo 2 de
cada 10 millones de tales funciones serian 'perfectas' para nuestros
propsitos. Esa tarea es factible slo en el caso de que los valores que
vayan a pertenecer a la tabla hash sean conocidas a priori. Existen
135

algoritmos para construir funciones hash perfectas que son utilizadas


para organizar las palabras clave en un compilador de forma que la
bsqueda de cualquiera de esas palabras clave se realice en tiempo
constante.
Las funciones que evitan valores duplicados son sorprendentemente
dificiles de encontrar, incluso para tablas pequeas. Por ejemplo, la
famosa "paradoja del cumpleaos" asegura que si en una reunin estn
presentes 23 ms presonas, hay bastante probabilidad de que dos de
ellas hayan nacido el mismo dia del mismo mes. En otras palabras, si
seleccionamos una funcin aleatoria que aplique 23 claves a una tabla
de tamao 365 la probabilidad de que dos claves no caigan en la misma
localizacin es de slo 0.4927.
En consecuencia, las aplicaciones h(k), a las que desde ahora
llamaremos funciones hash, tienen la particularidad de que podemos
esperar que h( ki ) = h( kj ) para bastantes pares distintos ( ki,kj ). El
objetivo ser pues encontrar una funcin hash que provoque el menor
nmero posible de colisiones (ocurrencias de sinnimos), aunque esto
es solo un aspecto del problema, el otro ser el de disear mtodos de
resolucin de colisiones cuando stas se produzcan.
2. FUNCIONES HASH.
El primer problema que hemos de abordar es el clculo de la funcin
hash que transforma claves en localizaciones de la tabla. Ms
concretamente,

necesitamos

una

funcin

que

transforme

claves(normalmente enteros o cadenas de caracteres) en enteros en un


rango [0..M-1], donde M es el nmero de registros que podemos
manejar con la memoria de que dispongamos.como factores a tener en
cuenta para la eleccin de la funcin h(k) estn que minimice las
colisiones y que sea relativamente rpida y fcil de calcular, aunque la
situacin ideal sera encontrar una funcin h que generara valores
aleatorios

uniformemente

sobre

el

intervalo

[0..M-1].

Las

dos

aproximaciones que veremos estn encaminadas hacia este objetivo y


ambas estn basadas en generadores de nmeros aleatorios.

136

Hasing Multiplicativo.
Esta tcnica trabaja multiplicando la clave k por s misma o por una
constante, usando despus alguna porcin de los bits del producto como
una localizacin de la tabla hash.
Cuando la eleccin es multiplicar k por s misma y quedarse con alguno
de los bits centrales, el mtodo se denomina el cuadrado medio. Este
metodo an siendo simple y pudiendo cumplir el criterio de que los bits
elegidos para marcar la localizacin son funcin de todos los bits
originales de k, tiene como principales inconvenientes el que las claves
con muchos ceros se reflejarn en valores hash tambin con muchos
ceros, y el que el tamao de la tabla est restringido a ser una potencia
de 2.
Otro mtodo multiplicativo, que evita las restricciones anteriores consiste
en calcular h(k) = Int[M * Frac(C*k)] donde M es el tamao de la tabla y
0 <= C <= 1, siendo importante elegir C con cuidado para evitar efectos
negativos como que una clave alfabtica K sea sinnima a otras claves
obtenidas permutando los caracteres de k. Knuth (ver bibliografa)
prueba que un valor recomendable es:

Hasing por Divisin.


En este caso la funcin se calcula simplemente como h(k) = k mod M
usando el 0 como el primer ndice de la tabla hash de tamao M.
Aunque la frmula es aplicable a tablas de cualquier tamao es
importante elegir el valor de M con cuidado. Por ejemplo si M fuera par,
todas las claves pares (resp. impares) seran aplicadas a localizaciones
pares (resp. impares), lo que constituira un sesgo muy fuerte. Una regla
simple para elegir M es tomarlo como un nmero primo. En cualquier
caso existen reglas mas sofisticadas para la eleccin de M (ver Knuth),
basadas todas en estudios toricos de funcionamiento de los mtodos
congruenciales de generacin de nmeros aleatorios.

137

3. RESOLUCIN DE COLISIONES.
El segundo aspecto importante a estudiar en el hasing es la resolucin
de colisiones entre sinnimos. Estudiaremos tres mtodos basicos de
resolucin de colisiones, uno de ellos depende de la idea de mantener
listas enlazadas de sinnimos, y los otros dos del clculo de una
secuencia de localizaciones en la tabla hash hasta que se encuentre que
se encuentre una vaca. El anlisis comparativo de los mtodos se har
en base al estudio del nmero de localizaciones que han de examinarse
hasta determinar donde situar cada nueva clave en la tabla.
Para todos los ejemplos el tamao de la tabla ser M=13 y la funcin
hash h1(k) que utilizaremos ser:
HASH = Clave Mod M
y los valores de la clave k que consideraremos son los expuestos en la
siguiente tabla:

Suponiendo que k=0 no ocurre de forma natural, podemos marcar todas


las localizaciones de la tabla, inicialmente vacas, dndoles el valor 0.
Finalmente y puesto que las operaciones de bsqueda e insercin estn
muy relacionadas, se presentaran algoritmos para buscar un item
insertndolo si es necesario (salvo que esta operacin provoque un
desbordamiento de la tabla) devolviendo la localizacin del item o un -1
(NULL) en caso de desbordamiento.

138

Encadenamiento separado o Hasing Abierto.


La manera ms simple de resolver una colisin es construir, para cada
localizacin de la tabla, una lista enlazada de registros cuyas claves
caigan en esa direccin. Este mtodo se conoce normalmente con el
nombre de encadenamiento separado y obviamente la cantidad de
tiempo requerido para una bsqueda depender de la longitud de las
listas y de las posiciones relativas de las claves en ellas. Existen
variantes dependiendo del mantenimiento que hagamos de las listas de
sinnimos (FIFO, LIFO, por valor Clave, etc), aunque en la mayora de
los casos, y dado que las listas individuales no han de tener un tamao
excesivo, se suele optar por la alternativa ms simple, la LIFO.
En cualquier caso, si las listas se mantienen en orden esto puede verse
como una generalizacin del mtodo de bsqueda secuencial en listas.
La diferencia es que en lugar de mantener una sola lista con un solo
nodo cabecera se mantienen M listas con M nodos cabecera de forma
que se reduce el nmero de comparaciones de la bsqueda secuencial
en un factor de M (en media) usando espacio extra para M punteros.
Para nuestro ejemplo y con la alternativa LIFO, la tabla quedara como
se muestra en la siguiente figura:

139

A veces y cuando el nmero de entradas a la tabla es relativamente


moderado, no es conveniente dar a las entradas de la tabla hash el
papel de cabeceras de listas, lo que nos conducira a otro mtodo de
encadenamiento, conocido como encadenamiento interno. En este caso,
la unin entre sinnimos est dentro de la propia tabla hash, mediante
campos cursores (punteros) que son inicializados a -1 (NULL) y que irn
apuntando hacia sus respectivos sinnimos.
Direccionamiento abierto o Hasing Cerrado.
Otra posibilidad consiste en utilizar un vector en el que se pone una
clave en cada una de sus casillas. En este caso nos encontramos con el
problema de que en el caso de que se produzca una colisin no se
pueden tener ambos elementos formando parte de una lista paraesa
casilla. Para solucionar ese problema se usa lo que se llama rehashing.
El rehashing consiste en que una vez producida una colisin al insertar
un elemento se utiliza una funcin adicional para determinar cual ser la
casilla que le corresponde dentro de la tabla, aesta funcin la
llamaremos funcin de rehashing,rehi(k).
A la hora de definir una funcin de rehashing existen mltiples
posibilidades, la ms simple consiste en utilizar una funcin que
140

dependa del nmero de intentos realizados para encontrar una casilla


libre en la que realizar la insercin, a este tipo de rehashing se le conoce
como hashing lineal. De esta forma la funcin de rehashing quedaria de
la siguiente forma:
rehi(k) = (h(k)+(i-1)) mod M i=2,3,...

En nuestro ejemplo, despus de insertar las 7 primeras claves nos


aparece la tabla A, (ver la tabla siguiente). Cuando vamos a insertar la
clave 147, esta queda situada en la casilla 6, (tabla B) una vez que no se
han encontrado vacas las casillas 4 y 5. Se puede observar que antes
de la insercin del 147 haba agrupaciones de claves en las
localizaciones 4,5 y 7,8, y despus de la insercin, esos dos grupos se
han unido formando una agrupacin primaria mayor, esto conlleva que si
se trata de insertar un elemento al que le corresponde algunas de las
casillas que estn al principio de esa agrupacin el proceso de
rehashing tendr de recorrer todas esas casillas con lo que se degradar
la eficiencia de la insercin. Para solucionar este problema habr que
buscar un mtodo de rehashing que distribuya de la forma ms aleatoria
posible las casillas vacas.
Despues de llevar a cabo la insercin de las claves consideradas en
nuestro ejemplo, el estado de la tabla hash ser el que se puede
observar en la tabla (C) en la que admas aparece el nmero de
intentos que han sido necesarios para insertar cada una de las claves.

141

Para intentar evitar el problema de las agrupaciones que acabamos de


ver podramos utilizar la siguiente funcin de rehashing:
rehi(k) = (h(k)+(i-1)*C) mod M C>1 y primo relativo con M
pero aunque esto evitara la formacin de agrupaciones primarias, no
solventara el problema de la formacin de agrupaciones secundarias
(agrupaciones separadas por una distancia C). El problema bsico de
rehashing lineal es que para dos claves distintas que tengan el mismo
valor para la funcin hash se irn obteniendo exactamente la misma
secuencia de valores al aplicar la funcin de rehashing, cunado lo
interenante seria que la secuencia de valores obtenida por el proceso de
rehashing fuera distinta. As, habr que buscar una funcin de rehashing
que cumpla las siguientes condiciones:

Sea fcilmente calculable (con un orden de eficiencia constante),

que evite la formacin de agrupaciones,

que genere una secuencia de valores distinta para dos claves distintas
aunque tenga el mismo valor de funcin hash, y por ltimo

que garantice que todas las casillas de la tabla son visitadas.


si no cumpliera esto ltimo se podra dar el caso de que an quedaran
casillas libres pero no podemos insertar un determinado elemento
142

porque los valores correspondientes a esas casillas no son obtenidos


durante el rehashing.
Una funcin de rehashing que cumple las condiciones anteriores es la
funcin de rehashing doble. Esta funcin se define de la siguiente forma:
hi(k) = (hi-1(k)+h0(k)) mod M i=2,3,...
con h0(k) = 1+k mod (M-2) y h1(k) = h(k).
Existe la posibilidad de hacer otras elecciones de la funcin h 0(k)
siempre que la funcin escogida no sea constante.
Esta forma de rehashing doble es particularmente buena cuando M y M2 son primos relativos. Hay que tener en cuenta que si M es primo
entonces es seguro que M-2 es primo relativo suyo (exceptuando el
caso trivial de que M=3).
El resultado de aplicar este mtodo a nuestro ejemplo puede verse en
las tablas siguientes. En la primera se incluyen los valores de h para
cada clave y en la segunda pueden verse las localizaciones finales de
las claves en la tabla as como las pruebas requeridas para su insercin.

143

4. BORRADOS Y REHASING.
Cuando intentamos borrar un valor k de una tabla que ha sido generada
por direccionamiento abierto, nos encontramos con un problema. Si k
precede a cualquier otro valor k en una secuencia de pruebas, no
podemos eliminarlo sin ms, ya que si lo hiciramos, las pruebas
siguientes para k se encontrarian el "agujero" dejado por k por lo que
podramos concluir que k no est en la tabla, hecho que puede ser
falso.Podemos comprobarlo en nuestro ejemplo en cualquiera de las
tablas. La solucin es que necesitamos mirar cada localizacin de la
tabla hash como inmersa en uno de los tres posibles estados: vacia,
ocupada o borrada, de forma que en lo que concierne a la busqueda,
una celda borrada se trata exectamente igual que una ocupada.En caso
de inserciones, podemos usar la primera localizacin vacia o borrada
que se encuentre en la secuencia de pruebas para realizar la operacin.
Observemos que este problema no afecta a los borrado de las listas en
el encadenamiento separado. Para la implementacin de la idea anterior
podria pensarse en la introduccin en los algortmos de un valor etiqueta
para marcar las casillas borradas, pero esto sera solo una solucin
parcial ya que quedara el problema de que si los borrados son

144

frecuentes, las bsquedas sin xitopodran requerir O(M) pruebas para


detectar que un valor no est presente.
Cuando una tabla llega a un desbordamiento o cuando su eficiencia baja
demasiado debido a los borrados, el nico recurso es llevarla a otra
tabla de un tamao ms apropiado, no necesariamente mayor, puesto
que como las localizaciones borradas no tienen que reasignarse, la
nueva tabla podra ser mayor, menor o incluso del mismo tamao que la
original. Este proceso se suele denominar rehashing y es muy simple de
implementar si el arca de la nueva tabla es distinta al de la primitiva,
pero puede complicarse bastante si deseamos hacer un rehashing en la
propia tabla.
5. EVALUACIN DE LOS MTODOS DE RESOLUCIN.
El aspecto ms significativo de la bsqueda por hashing es que si
eficiencia depende del denominado factor de almacenamiento = n/M
con n el nmero de items y M el tamao de la tabla.
Discuteremos el nmero medio de pruebas para cada uno de los
mtodos que hemos visto de resolucin de colisiones, en trminos de
BE (bsqueda con xito) y BF (bsqueda sin xito). Las demostraciones
de las frmulas resultantes pueden encontrarse en Knuth (ver
bibliografa).
Encadenamiento separado.
Aunque puede resultar engaoso comparar este mtodo con los otros
dos, puesto que en este caso puede ocurrir que >1, las frmulas
paroximadas son:

Estas expresiones se aplican incluso cuando >>1, por lo que para


n>>M, la longitud media de cada lista ser , y deberia esperarse en
media rastrear la mitad de la lista, antes de encontrar un determinado
elemento.
Hasing Lineal.
Las frmulas aproximadas son:

145

Como puede verse, este mtodo, aun siendo satisfactorio para


pequeos, es muy pobre cuando -> 1, ya que el lmite de los valores
medios de BE y BF son respectivamente:

En cualquier caso, el tamao de la tabla en el hash lineal es mayor que


en el encadenamiento separado, pero la cantidad de memoria total
utilizada es menor al no usarse punteros.
Hasing Doble.
Las frmulas son ahora:
BE=-(1/1-)

ln(1-)

BF=1/(1-)
con valores medios cuando -> 1 de M y M/2, respectivamente.
Para facilitar la comprensin de las frmulas podemos construir una
tabla en la que las evaluemos para distintos valores de :

La eleccin del mejor mtodo hash para una aplicacin particular puede
no ser fcil. Los distintos mtodos dan unas caractersticas de eficiencia
similares. Generalmente, lo mejor es usar el encadenamiento separado
para reducir los tiempos de bsqueda cuando el nmero de registros a
procesar no se conoce de antemano y el hash doble para buscar claves
cuyo nmero pueda, de alguna manera, predecirse de antemano.

146

En comparacin con otras tcnicas de bsqueda, el hashing tiene


ventajas y desventajas. En general, para valores grandes de n (y
razonables valores de ) un buen esquema de hashing requiere
normalmente menos pruebas (del orden 1.5 - 2) que cualquier otro
mtodo de bsqueda, incluyendo la bsqueda en rboles binarios. Por
otra parte, en el caso peor, puede comportarse muy mal al requerir O(n)
pruebas. Tambin puede considerarse como una ventaja el hecho de
que debemos tener alguna estimacin a priori de nmero mximo de
items que vamos a colocar en la tabla aunque si no disponemos de tal
estimacin siempre nos quedara la opcin de usar el metodo de
encadenamiento separado en donde el desbordamiento de la tabla no
constituye ningn problema.
Otro problema relativo es que en una tabla hash no tenemos ninguna de
las ventajas que tenemos cuando manejamos relaciones ordenadas, y
as p.e. no podemos procesar los items en la tabla secuencialmente, ni
concluir tras una bsqueda sin xito nada sobre los items que tienen un
valor cercano al que buscamos, pero en cualquier caso el mayor
problema que tener el hashing cerrado es el de los borrados dentro de la
tabla.

IMPLEMENTACIN DE LAS TABLAS HASH.

Implementacin de Hasing Abierto.


En este apartado vamos a realizar una implementacin simple del
hasing abierto que nos servir como ejemplo ilustrativo de su
funcionamiento. Para ello supondremos un tipo de dato char * para el
cual disearemos una funcin hash simple consistente en la suma de los
codigos ASCII que componen dicha cadena.
Una posible implementacin utilizando el tipo de dato abstracto lista
sera la siguiente:
#define NCASILLAS 100 /*Ejemplo de nmero de entradas en la tabla.*/
147

typedef tLista *TablaHash;

Para la cual podemos disear las siguientes funciones de creacin y


destrucin:

TablaHash CrearTablaHash ()
{
tLista *t;
register i;
t=(tLista *)malloc(NCASILLAS*sizeof(tLista));
if (t==NULL)
error("Memoria insuficiente.");
for (i=0;i<NCASILLAS;i++)
t[i]=crear();
return t;
}

void DestruirTablaHash (TablaHash t)


{
register i;
for (i=0;i<NCASILLAS;i++)
destruir(t[i]);
free(t);
}
Como fue mencionado anteriormente la funcin hash que ser usada es:
int Hash (char *cad)
{
int valor;
unsigned char *c;
for (c=cad,valor=O;*c;c++)
valor+=(int)(*c);

148

return(valor%NCASILLAS);
}
Y funciones del tipo MiembroHash, InsertarHash, BorrarHash pueden
ser programadas:
int MiembroHash (char *cad,TablaHash t)
{
tPosicion p;
int enc;
int pos=Hash(cad);
p=primero(t[pos]);
enc=O;
while (p!=fin(t[pos]) && !enc) {
if (strcmp(cad,elemento(p,t[pos]))==O)
enc=1;
else
p=siguiente(p,t[pos]);
}
return enc;
}
void InsertarHash (char *cad,TablaHash t)
{
int pos;
if (MiembroHash(cad,t))
return;
pos=Hash(cad);
insertar(cad,primero(t[pos]),t[pos]);
}
void BorrarHash (char *cad,TablaHash t)
{
tPosicion p;
int pos=Hash(cad);
p=primero(t[pos]);
while (p!=fin(t[pos]) && !strcmp(cad,elemento(p,t[pos])))
p=siguiente(p,t[pos]));
if (p!=fin(t[pos]))
borrar(p,t[pos]);
}

149

Como se puede observar esta implementacin es bastante simple de


forma que puede sufrir bastantes mejoras. Se propone como ejercicio el
realizar esta labor dotando al tipo de dato de posibilidades como:

Determinacin del tamao de la tabla en el momento de creacin.

Modificacin de la funcin hash utilizada, mediante el uso de un puntero


a funcin.

Construccin de una funcin que pasa una tabla hash de un tamao


determinado a otra tabla con un tamao superior o inferior.

Construccin de un iterador a travs de todos los elementos de la tabla.

etc...

Implementacin de Hasing Cerrado.


En este apartado vamos a realizar una implementacin simple del
hashing cerrado. Para ello supondremos un tipo de datochar * al igual
que en el apartado anterior, para el cual disearemos la misma funcin
hash.
Una posible implementacin de la estructura a conseguir es la siguiente:
#define NCASILLAS 100
#define VACIO NULL
static char * BORRADO='''';
typedef char **TablaHash;
Para la cual podemos disear las siguientes funciones de creacin y
destrucin:
TablaHash CrearTablaHash ()
{
TablaHash t;
register i;
t=(TablaHash)malloc(NCASILLAS*sizeof(char *));
if (t==NULL)
error("Memoria Insuficiente.");
for (i=0;i<NCASILLAS;i++)
t[i]=VACIO;
return t; }

150

void DestruirTablaHash (TablaHash t)


{
register i;
for (i=O;i<NCASILLAS;i++)
if (t[i]!=VACIO && t[i]!=BORRADO)
free(t[i]);
free t;
}
La funcin hash que ser usada es igual a la que ya hemos usado para
la

implementacin

del

Hasing

Abierto.

funciones

del

tipo

MiembroHash, InsertarHash, BorrarHash pueden ser programadas tal


como sigue, teniendo en cuenta que en esta implementacin haremos
uso de un rehashing lineal.
int Hash (char *cad)
{
int valor;
unsigned char *c;
for (c=cad, valor=0; *c; c++)
valor += (int)*c;
return (valor%NCASILLAS);
}
int Localizar (char *x,TablaHash t)
/* Devuelve el sitio donde esta x o donde deberia de estar. */
/* No tiene en cuenta los borrados.
*/
{
int ini,i,aux;
ini=Hash(x);
for (i=O;i<NCASILLAS;i++) {
aux=(ini+i)%NCASILLAS;
if (t[aux]==VACIO)
return aux;
if (!strcmp(t[aux],x))
return aux;
}
return ini; }

151

int Localizar1 (char *x,TablaHash t)


/* Devuelve el sitio donde podriamos poner x */
{
int ini,i,aux;
ini=Hash(x);
for (i=O;i<NCASILLAS;i++) {
aux=(ini+i)%NCASILLAS;
if (t[aux]==VACIO || t[aux]==BORRADO)
return aux;
if (!strcmp(t[aux],x))
return aux;
}
return ini;
}
int MiembroHash (char *cad,TablaHash t)
{
int pos=Localizar(cad,t);
if (t[pos]==VACIO)
return 0;
else
return(!strcomp(t[pos],cad));
}
void InsertarHash (char *cad,TablaHash t)
{
int pos;
if (!cad)
error("Cadena inexistente.");
if (!MiembroHash(cad,t)) {
pos=Localizar1(cad,t);
if (t[pos]==VACIO || t[pos]==BORRADO) {
t[pos]=(char *)malloc((strlen(cad)+1)*sizeof(char));
strcpy(t[pos],cad);
} else {
error("Tabla Llena. \n");
}
}
}
void BorrarHash (char *cad,TablaHash t)
{
152

int pos = Localizar(cad,t);


if (t[pos]!=VACIO && t[pos]!=BORRADO) {
if (!strcmp(t[pos],cad)) {
free(t[pos]);
t[pos]=BORRADO;
}
}
}

Obviamente, esta implementacin al igual que la del hasing abierto es


tambin mejorable de forma que se propone el ejercicio de disear e
implementar una versin mejorada con posibilidades similares a las
enumeradas en el apartado anterior.

rboles de bsqueda externa.


rboles Binarios de Bsqueda
Son difciles de equilibrar, Si no estn equilibrados el nmero de
consultas para encontrar un elemento es indeterminado.
Conforme aumentamos el nmero de registros aumenta tambin el
nmero de consultas para hacer bsquedas. Aumenta la profundidad
Estas estructuras se usan para mantener ordenadas listas de valores:

rboles B
Son rboles de bsqueda de n ramas (no binarios).Desarrollados por
RudolfBayer yEduard M. McCreight (Boeing).
Son uno de los sistemas ms utilizados para indexar informacin
almacenada en ficheros.
Solucionan los problemas de los ABB.
Caractersticas de los rboles B
Orden de un rbol B: nmero mximo de ramas que pueden partir

153

de cada nodo. (Llamaremos Nal orden)Si un nodo tiene K ramas entonces


tieneK-1 claves. Todos los nodos hoja estn al mismo nivel.Todos los nodos
intermedios, excepto el raz, deben tener entre N/2 y N ramas. El nodo raz, o
es una hoja, o al menos tiene 2 ramas. No se permiten claves repetidas.

rboles-B
Podemos limitar el nmero mximo de consultas al hacer una
bsqueda imponiendo una profundidad mxima hemos de
aumentar el orden.
El tamao de un nodo debera ser lo ms prximo posible al
tamao de un cluster (aunque menor).
Sobre la eficiencia y el orden:
Estos rboles se suelen usar para indexar datos en disco:
Mayor orden
Menor profundidad
El orden se calcula en funcin del tamao de un cluster de disco.

ALGORITMOS DE BSQUEDA Y ORDENACIN


Bsqueda Binaria Recursiva
Ejemplos: bsqueda de un telfono en el listn telefnico o juego de
adivinar un nmero
El algoritmo de bsqueda binaria requiere que la lista en la que se
realiza la bsqueda est ordenada
Este problema tambin se puede resolver utilizando recursividad
Probablemente se trate de la aplicacin ms sencilla de Divide y
Vencers, aunque hablando con propiedad, se trate de un tcnica
de reduccin (o simplificacin), ya que la solucin de todo caso
Suficientemente grande se reduce a un solo caso ms pequeo de
154

tamao la mitad
Funcin Busq_Bin_Rec (x:tipoelem; A:lista; ini,fin:tipoposicion): tipoposicion;
Variables
mitad: tipoposicion;
Inicio
Si (fin > ini)
Entonces Bus_Bin_Rec _ posicion_nula
Si_No
Si (ini = fin)
Entonces Si A[ini] = x
Entonces Bus_Bin_Rec _ ini
Si_No Bus_Bin_Rec _ posicion_nula
Fin_Si
Si_No mitad _ pos_central(A, ini, fin);
Si x <= A[mitad]
Entonces Bus_Bin_Rec _ Bus_Bin_Rec (x,A,ini,mitad)
Si_No Bus_Bin_Rec _ Bus_Bin_Rec (x,A,mitad+1,fin);
Fin_Si
Fin_Si
Fin_Si
Fin.

Algoritmos de Ordenacin Interna Avanzados


Existen algoritmos clsicos de ordenacin que siguen el esquema de Divide y
Vencers
Es interesante observar que aunque ambos siguen el mismo esquema son
totalmente diferentes
Mergesort
Aqu el enfoque de Divide y Vencers consiste en descomponer el vector
original en dos mitades de igual tamao (si el nmero de elementos es impar,
una mitad tendr un elemento ms que la otra)

155

Se ordenan estas dos mitades mediante llamadas recursivas


Finalmente se mezclan las soluciones ordenadas, manteniendo el orden final

Algoritmos de Ordenacin Externa


Ordenacin externa:
En la actualidad es muy comn procesar tales volmenes de informacin que
los datos no puedan almacenarse en la memoria principal de la computadora.
Esto datos, Organizado en archivos, se guardan en dispositivos de
almacenamiento secundario tales como cintas, discos, etc.
El proceso de ordenar los datos almacenados en varios archivos se conoce con
el nombre de fusin o mezcla, entendiendo por este concepto la combinacin o
intercalacin de dos o mas secuencias ordenada en una nica secuencia
ordenada. Debe hacerse hincapi en que solo se colocan en la memoria
principal de la computadora los datos que se pueden accesar directamente.

Los datos estn en un dispositivo de almacenamiento externo (ficheros) y su


ordenacin es ms lenta que la interna.

Introduccin concepto de Ordenacin Externa


Problema: queremos ordenar un conjunto de elementos y estos no caben en
memoria principal
Solucin: Ordenacin Externa
Es distinta de la Ordenacin Interna, pues el acceso a
archivos externos es bastante ms limitado
A veces las estructuras de datos deben adaptarse para que
los dispositivos de memoria perifrica puedan hacer frente, con rapidez, a las
necesidades del algoritmo de clasificacin.
En consecuencia, la mayora de los algoritmos de ordenacin interna que
hemos estudiado no se adaptan para la ordenacin externa
Clasificacin de los algoritmos de Ordenacion Externa

156

Los algoritmos de ordenacin externa se basan esencialmente en el mtodo


conocido como Ordenacin por Fusin
Estos algoritmos pueden dividirse en dos grupos: los que utilizan mtodos de
ordenacin interna y los que no
a) Algoritmos que utilizan Ordenacin Interna
Se basan en dividir un archivo en dos o ms particiones, ordenarlas por
separado y posteriormente mezclarlas en un nuevo archivo ordenado
Se distinguen 3 fases:
1.- Particiones / Ordenacin Interna. Los registros se leen del archivo de
entrada (no ordenado) y se divide en particiones de registros. Estos conjuntos
de registros se ordenan utilizando algn mtodo interno. Cada particin
ordenada se guarda en
un archivo de salida
2.- Mezcla. Se combinan los archivos de las particiones mediante mezclas
sucesivas, hasta obtener un nico archivo o particin que contiene al archivo
original ordenado.
3.- Salida. Se copia el archivo ordenado en un dispositivo de almacenamiento
masivo
Los distintos algoritmos de ordenacin de archivos por mezcla difieren unos
de otros en los siguientes puntos:
_Algoritmo de ordenacin interna aplicado
_ Cantidad de espacio en memoria principal asignado a la ordenacin interna
_ Distribucin de sublistas ordenadas en almacenamiento secundario
_ Nmero de sublistas ordenadas que se mezclan juntas en cada paso de
mezcla
Por ejemplo, si suponemos un archivo F1 que queremos ordenarsegn la clave
K, los pasos a seguir son los siguientes:
1. Dividir F1 en dos particiones F11 y F12. Para ello se puede ir leyendo y
copiando alternativamente cada registro de F en cada archivo de salida, o bien
leer M registros de F y copiarlos en F11, los siguientes M en F12, los siguientes
M
en F11, etc.. O bien utilizar cualquier otra tcnica de particin
2. Ordenar las particiones F11 y F12 por la clave K

157

3. Mezclar las particiones F11 y F12 en un nuevo archivo F2 ordenado por la


clave K. (Recordar el subalgoritmo mezclar utilizado en el algoritmo mergesort;
salvo que aqu se trabaja directamente con archivos)
4. Escribir el archivo F2 en un dispositivo de memoria auxiliar
b) Algoritmos que no utilizan Ordenacin Interna
Se basan en distintas combinaciones de procesos de particin y fusin de
archivos
Utilizan archivos temporales para almacenar las particiones. Por tanto se
debe prever espacio suficiente en el dispositivo de almacenamiento masivo
utilizado
El mtodo de fusin visto anteriormente supone que los archivos se
encuentran ordenados. Si no lo estn se utilizan otros mtodos de mezcla El
ms comn de todos ellos es el de Ordenacin por Mezcla Directa

Algoritmos de Bsqueda y Ordenacin Avanzados


Ordenacin por Mezcla Directa:
Realiza una particin sucesiva del archivo y una fusin que produce
secuencias ordenadas
La primera particin se hace para secuencias de longitud 1 y la fusin
producir secuencias ordenadas de longitud 2.
A cada nueva particin y fusin se duplicar la longitud de las secuencias
ordenadas
El mtodo terminar cuando la longitud de la secuencia ordenada excede de
la longitud del archivo a ordenar
Por ejemplo, consideremos el siguiente archivo:
F: 19 27 2 8 36 5 20 15 6
Lo dividimos en secuencias de longitud 1 y obtenemos F1 y F2
F1: 19 2 36 20 6

158

F2: 27 8 5 15
Ahora se funden los archivos F1 y F2 formando pares ordenados:
F: 19 27 2 8 5 36 15 20 6
Se vuelve a dividir F en partes iguales y en secuencias de longitud 2
F1: 19 27 5 36 6
F2: 2 8 15 20
F1 y F2 se funden utilizando las secuencias de longitud 2:
F: 2 8 19 27 5 15 20 36 6
Se dobla la longitud de la secuencia (4) y se vuelve a dividir F
F1: 2 8 19 27 6
F2: 5 15 20 36
La fusin de las secuencias de 4 elementos producen el nuevo archivo F.
F: 2 5 8 15 19 20 27 36 6
Se realiza una nueva particin con secuencias de longitud 8.
F1: 2 5 8 15 19 20 27 36
F2: 6
Por ltimo la fusin dar:
F: 2 5 6 8 15 19 20 27 36

Algoritmos de Bsqueda y Ordenacin Avanzados


Intercalacin de archivos
Por intercalacin de archivo se entiende la unin o fusin de dos o mas
archivos,
Ordenado deacuerdo con un determinado campo clave, en un solo archivo.
Vase un Ejemplo.
EJEMPLO
Supngase que se tienen dos archivo F1 y F2, ordenado, de acuerdo a un
campo Clave:
F1:06 09 18 20 35 ARCHIVO
F2:10 16 25 28 66 82 87ARCHIVO

159

Debe producirse entonces un archivo f3 ordenado, como resultado de la


mezcla
de F1 y F2.solo pueden ser accesadas directamente dos claves ,la primera del
archivo
F1 y la segunda del archivo F2.Las comparaciones que se realizan para
producir el archivo F3 son las siguientes:
(06<10) si se cumple la condicin
se escribe 06 en el archivo de salida F3 y se vuelve a leer otra clave de
F1 (09)
(09<10) si se cumple la condicin
se escribe 09 en el archivo de salida F3 y se vuelve a leer otra clave de
F1 (18)
(18<10) no se cumple la condicin
se escribe 10 en el archivo de salida F3 y se vuelve a leer otra clave de
F2(16)
El estado de los archivos F1F2 y F3 es hasta el momento el siguiente:

F1:06 09 18 20 35
F2:10 16 25 28 66 82 87
F3:06 09 10
El proceso continua hasta que en uno u otro archivo se detecte el fin de archivo
en tal caso solo tendrn que transcribir las claves del archivo no vaco al
archivo de
Salida F3.Este es el resultado final de la intercalacin entre los archivos F1 y
F2:
F3:06 09 10 16 18 20 25 28 35 66 82 87
Ahora analcese el algoritmo de intercalacin de archivos.
160

Algoritmo Intercalacin
INTERCALACIN(F1,F2,F3)
{El algoritmo intercala los elementos de dos archivo ya ordenado F1 y F2 y
almacena el resultado en el archivo F3 }
{R1 y R2 son variables de tipo entero BAND variable de tipo booleano}
1.Abrir los archivo F1 y F2 para lectura
2.Abrir el archivo F3 para escritura
3.leer R1 de F1 y R2 de F2
{R1y R2 son las primera claves de F1 y F2 respectivamente }
4. Repetir mientras (no sea el fin del archivo de F1) y (no sea el fin de archivo
de F2)
4.1 si R1< R2
entoces
Escribir R1 en F3
Leer R1 de F1
Si no
Escribir R2 en F3

Leer R2 de F2
4.2{fin del condicional del paso 4.1}
5. {fin del ciclo del paso 4}
6. hacer BAND

FALSO

7. Repetir mientras(no se el fin del archivo de F1)o (no se el fin del


archivo de F2)
7.1 si (fin de archivo de F1)
entonces
7.1.1 SI R1< R2
Entonces
Escribir R1 en R3
Escribir R2 en R3
7.1.1.1 Repetir mientras(no se a el fin de archivo de F2)
161

leer R2 de F2
Escribir R2 en F3
7.1.1.2 {fin del ciclo del paso 7.1.1.1}
Hacer BAND VERDADERO
Si no
Escribir R2 en F3
Leer R1 de F2
7.1.2 {Fin del condicional del paso 7.1.1}
si no
7.1.3 si R1 < R2
entonces
Escribir R1 en F3
Leer R1 de F1
Si no
Escribir R2 en F3
Escribir R1 en F3
7.1.3.1 Repetir mientras (no se el fin de archivo de F1)
Leer R1 de F1
Escribir R1 en F3
7.1.3.2

{ Fin del ciclo del paso 7.1.3.1 }

Hacer BAND
7.1.4
7.2

VERDADERO

{Fin del condicional del paso 7.1.3}

{Fin del condicional del paso 7.1 }

8. {Fin del ciclo del paso 7 }


9. si BAND = FALSO entonces
9.1 si R1 <R2
entonces
Escribir R1 en F3
Escribir R2 en F3
Si no
Escribir R2 en F3
Escribir R1 en F3
9.2 {Fin del condicional del paso 9.1 }

162

10. {Fin del condicional del paso 9 }

La ordenacin de archivo se lleva a cabo cuando el volumen de los datos a


tratar es demasiado grande y los mismos no caben en la memoria principal de
la computadora Al ocurrir esta situacin no pueden aplicarse los metodos de
ordenacin interna estudiados en la primera parte de es capitulo, de modo que
debe pensarse en otro tipo de algoritmos para ordenar datos almacenados en
archivos.
Por ordenacin de archivo se entiende, entonces,la ordenacin o clasificacion
de
Estos, ascendente o descendentemente, de acuerdo con un campo
determinado al que se denominara campo clave. La principal desventaja de
esta ordenacin es el tiempo de ejecucin, debido a las sucesivas operacione
de entrada y salida.
Los dos metodos de ordenacin externa ms importantes son los basado en la
mezcla directa y en la mezcla equilibrada.

1. ADMINISTRACIN DE MEMORIA.
MS-DOS , es un sistema operativo monotarea, usualmente un usuario lanza un
programa escribiendo el nombre del archivo ejecutable luego del indicador de
comandos del COMMAND. El cargador es disponible a travs de la funcin 4Bh
de la INT 21h, el despachador del DOS. Este asigna al programa un bloque de
memoria suficientemente grande para alojarlo, copia en el una imagen del
programa obtenida del archivo ejecutable, actualiza los registros de la CPU y
algunas estructuras del sistema, y por ultimo da el control de ejecucin al
programa.
La asignacin de memoria en los segmentos es responsabilidad de diferentes
componentes en diferentes etapas en el sistema, a saber la etapa de
compilacin (compiladores), en lace (enlazadores o linkers)y carga (cargador).

163

Un programa fuente puede estar formado por uno o un conjunto de archivos en


donde se define indistintamente variables, funciones y /o procedimientos, etc.
Por ejemplo en C
Se usa el encabezado externo para indicar que una funcin definida en un
archivo puede ser utilizada por otro archivo despus de su declaracin
correspondiente.
En el caso de que un programa este definido mediante un conjunto de archivos
fuentes, el compilador genera el cdigo objeto de cada uno de los archivos
correspondiente. Luego es el enlazador el encargado de generar un solo
archivo ejecutable a partir de todos los mdulos objetos. Finalmente el
cargador es el encargado de cargar en memoria el archivo ejecutable.
COMPILACIN
Durante el proceso de compilacin se generan los archivos objetos de cada
uno de los archivos fuentes del programa. Las referencias externas no son
resueltas en esta etapa.

ENLACE
En esta etapa se reagrupan todos los archivos objetos pertenecientes al
programa y se genera un solo archivo ejecutable. Las referencias externas son
resueltas aqu, as como las llamadas a funciones de biblioteca del sistema.

CARGA
Durante esta etapa se carga en memoria el programa ejecutable, reservndose
primero espacio de memoria suficiente mente grande para la imagen del
programa. Luego de algunas actualizaciones de estructuras del sistema,
comienza la ejecucin del programa. El mecanismo de administracin de
memoria podra permitir la comparticin de algunos bloques de memoria
comunes entre diferentes procesos, en particular aquellos que contiene cdigo

164

ejecutable no auto modificable, con el fin de hacer ms eficiente el uso de la


memoria.
ASIGNACIN DE MEMORIA EN TIEMPO DE EJECUCIN.
Alternativamente tambin es posible que los procesos adquieran y liberen
memoria del sistema en forma dinmica utilizando, por ejemplo, funciones en C
como malloc o free. Estas operaciones de asignacin y liberacin son
realizadas en conjunto entre el compilador y el sistema operativo. Es decir,
ellas son especificadas en los cdigos fuentes de los programas y chocadas a
cabo en tiempo de ejecucin. Es claro que el resultado final de las operaciones
depender del esquema actual de uso de la memoria.
En trminos generales, la asignacin de memoria para los procesos en
ejecucin puede realizarse en el momento de compilacin o enlace, en el
momento de carga o en tiempo de ejecucin. Esto significa que el cdigo
resultante del enlace de los mdulos genera cdigo absoluto, es decir que las
direcciones de memoria a las que se

haga referencia sern direcciones

absolutas, por lo tanto el xito de la ejecucin de los programas depender de


que se carguen en memoria a partir de una direccin absoluta especifica.
Adems, con un esquema como este prcticamente imposible desplazar
programas de una localizacin de memoria a otra.
RELOCALIZACION.
Es el mecanismo por medio del cual es posible cambiar dinmicamente todas
las referencias a direcciones de memoria en los programas si cambia la
direccin inicial del bloque de memoria donde se aloja el programa, entre ella,
la direccin donde se encuentra cada referencia a memoria en el programa.
Algunos sistemas relocalizan los programas solo en tiempo de carga. Este es el
caso de MS-DOS con los programas asociados con archivos .EXE (un
programa asociado con un archivo .COM no necesita ser relocalizado.). en tal
caso no es posible desplazar los contenidos de un programa en la memoria en
el tiempo de ejecucin, estos si permiten el desplazamiento de los obtenidos de
programas en memoria con el fin de hacer bloques de memoria disponibles
suficientemente grandes.

165

1.1 ADMINISTRACIN DE MEMORIA SIN INTERCAMBIO


Dentro de este tipo de administracin se encuentran los sistemas monotareas,
es decir, que mantienen en memoria solo un proceso de usuario, por lo tanto no
es posible ejecutar concurrentemente mas de un proceso de usuario en el
sistema; y los sistemas multiprogramados, es decir que permiten mantener mas
de un proceso de usuario en memoria. Dentro de este ltimo esquema existen
sistemas que manejan multiprogramacin con particiones fijas de memoria y
sistemas que manejan la multiprogramacin con particiones variables de
memoria. La caracterstica que poseen en comn estos tres tipos de esquema
de administracin es que ninguno de ellos permite intercambiar procesos entre
memoria y disco, es decir que los procesos no pueden permanecer por
periodos de tiempo en disco, sino que durante el tiempo que dure su ejecucin
necesariamente deben permanecer en memoria completamente cargados.
El esquema sin intercambio puede ser practico para sistemas en donde la
cantidad de procesos presente es manejable por la CPU y la memoria. Sin
embargo, para aquellos sistemas donde

por lo general existen muchos

usuarios los cuales ejecutan muchos procesos, la capacidad de la CPU y de la


memoria no es suficiente para satisfacer las necesidades de todos los procesos
en cuestin. En este ultimo caso, es atractiva la idea de aprovechar mejor los
recursos, as, por ejemplo, aquellos procesos que cambian del estado listo al
estado bloqueado puedan ser almacenados temporalmente en disco para luego
continuar su ejecucin y as darle la oportunidad de ser ejecutados a otros
procesos.
MONOPROGRAMACION SIN INTERCAMBIO.
A este tipo de administracin tambin se le conoce con el nombre de
asignacin de una sola particin y corresponde al esquema de administracin
mas sencillo y barato, en el cual no existe ninguna divisin lgica de la
memoria. Sin embargo este esquema no se utiliza mucho, en su lugar el
esquema mas sencillo que se implementa usualmente es el llamado monitor
residente y consiste en que la memoria se divide en dos espacios; uno par que
resida el sistema operativo y otro espacio queda disponible al proceso de
usuario.

166

Este tipo de administracin se caracteriza porque, en primer lugar, es


monousuario, es decir, soporta un usuario y este tiene disponible toda la
memoria de usuario. En este caso el usuario solo puede cargar un proceso en
memoria cada vez. En el caso de tener un sistema compuesto por un espacio
de memoria para el sistema operativo y otro espacio para el proceso de usuario
tiene que existir mecanismos de proteccin que evite que un proceso de
usuario perturbe el comportamiento del sistema operativo, accediendo
libremente al espacio de memoria del sistema. Dicha proteccin por lo general
es proporcionada por el hardware.
MULTIPROGRAMACION.
Consiste en que el espacio de memoria de usuario es compartido por mltiples
procesos, es decir que pueden ser almacenados en memoria un conjunto de
procesos simultneamente. La utilizacin de multiprogramacin trae consigo
varias ventajas entre las cuales se cuenta el mejor aprovechamiento de la
CPU, pues cuando un proceso esta realizando operaciones de entrada y salida,
la CPU podra ser utilizada por otro proceso; por otro lado la multiprogramacin
es atractiva para los sistemas multiusuarios y procesos interactivos.
MULTIPROGRAMACION CON PARTICIONES FIJAS.
Bajo este esquema de administracin la memoria es dividida en n partes, en
donde cada una de esas partes pueden ser de tamaos distintos, cada proceso
es asignado a una de estas particiones cundo entra en ejecucin. La
planificacin

de la CPU es, por lo tanto, afectada por el esquema de

administracin de memoria que se implemente. Existen fundamentalmente dos


esquemas mediante los cuales se pueden asignar los procesos a una particin.
Un esquema consiste en que cada particin tiene una cola de procesos
asociados que desean ser cargados en esa particin o bien existen una sola
cola en donde se va asignando una particin a cada proceso desde el
comienzo de la cola.
En el primer caso de cada particin esta asociada con una lista de procesos
cuyas necesidades pueden ser satisfechas por la particin. Existen algunos
problemas como consecuencia de este esquema, primero, puede formar una

167

cola de espera con muchos procesos mientras que otra particin pudiera estar
vaca, a este problema se le denomina
Fragmentacin externa.
Una mejora a este esquema podra conseguirse permitindose que si una
particin se vaca, se le asigne un proceso asociado a otra particin.
Este esquema de administracin contempla la proteccin de las particiones,
que permita proteger cada de las particiones de las particiones adyacentes,
esto significa que cada proceso de usuario, as como el propio sistema
operativo debe estar confiado que no se vera perturbado por otro proceso. En
este caso, como en la mayora de los esquemas de administracin de memoria,
la proteccin debe estar apoyada en las particularidades del hardware. Un
mecanismo de proteccin, que a su vez garantiza relocalizacin dinmica se
tiene mediante el uso de dos registros de CPU, un registro llamado base y un
registro llamado limite. El registro base indica la direccin absoluta de inicio de
la particin y el registro limite indica el tamao de la particin. El espacio de
direcciones virtuales de un programa va de la direccin de comienzo 0 hasta
una direccin mxima(siempre menor que limite).
Cuando un programa genera una direccin de memoria (perteneciente al
espacio de direcciones virtuales del programa.), esta se compara con el
contenido del registro lmite, si es mayor o igual el hardware genera una
trampa, sino se suma al contenido del registro base para obtener la direccin
absoluta resultante (que estar en la propia particin). Entonces no es
necesario relocalizar el programa en ningn momento, incluso es posible mover
un programa de una particin a otra.
Cuando el proceso se hace activo tambin la particin donde reside
asignndose a los registrndose base y limite los valores correspondiente a
esta particin. A continuacin se muestra de que manera actan estos
registros.
R. Limite

168

ERROR
CPU

>

Direccin Particin
+

R.Base

Un problema asociado con el modelo de particiones fijas esta asociado con la


eleccin del numero n de particiones. Este nmero representa al grado de
multiprogramacin del sistema, de modo que debe tener el mayor valor posible
permitiendo que existan particiones suficientemente grandes para alojar los
procesos ms grandes. Con este tipo de administracin se presenta un
problema denominado fragmentacin interna, el cual consiste en que si el
proceso no utiliza la totalidad del espacio de la particin entonces se produce
perdida de memoria, es decir, este espacio de memoria no puede ser utilizado
por otro proceso, aun cuando existan procesos en espera que puedan
satisfacer con este espacio. La fragmentacin interna es una caracterstica
indeseable de este modelo pues atenta contra la eficiencia de un sistema. Una
solucin a este problema se tiene con el esquema de particiones variables.
El modelo de particiones fijas puede ser mejorado con el intercambio. Es decir,
podra permitirse que algunos procesos cargados en algunas particiones se
guarden temporalmente en disco para asignar la particin a otro proceso,
llevndose a cabo un intercambio de procesos entre el disco y memoria.

1.2 ADMINISTRACIN CON INTERCAMBIO


El intercambio es un esquema que hace del espacio de disco una extensin de
la memoria que resulta til cuando los sistemas requieren contener mas
procesos que

los que

la

memoria

soporta,

principalmente

sistemas

multiusuarios e interactivos. En este caso el mecanismo de intercambio permite


intercambiar procesos entre la memoria principal y el disco.
MULTIPROGRAMACION CON PARTICIONES VARIABLES.

169

Consisten en que la memoria se divide en un conjunto de particiones que se


caracterizan por que su tamao es variable y se crean dinmicamente en el
transcurso de la ejecucin de los distintos procesos en el sistema. Este tipo de
administracin, al igual que el modelo de particiones fijas se puede efectuar
con intercambio, es decir, que un proceso que esta en memoria puede ser
traspasado a disco en forma temporal para darle la posibilidad a otro proceso
de que se ejecute.
Al igual que la administracin con particiones fijas este tipo de administracin
consiste en el almacenamiento del espacio de direcciones virtuales del proceso
completo en memoria para que este pueda ser ejecutado. Cuando un proceso
ingresa al sistema, se le busca un bloque de memoria disponible lo
suficientemente grande para alojarlo, si se encuentra uno se le asigna al
proceso justamente la cantidad que necesita, mantenindose el resto
disponible. Siempre que se carga un proceso se efecta la secuencia anterior.
La memoria de usuario se ve como una sucesin de bloques contiguos
ocupados por algn proceso disponible. Es lgico que no habrn dos bloques
disponibles consecutivos, aunque si pueden haber varios bloques ocupados
consecutivos.
Cuando un proceso entra al sistema se busca un bloque disponible de memoria
que satisfaga los requerimientos de espacio del proceso segn alguna
estrategia de bsqueda. Cuando un proceso termina libera el bloque que ocupa
y este pasa a ser disponible, posiblemente anexndose a otro bloque
disponible adyacente.
La asignacin de memoria a los distintos procesos que la requieren se puede
llevar acabo de acuerdo a algunos criterios conocidos. Las particiones
disponibles deben ser otorgadas a los procesos de la manera que el sistema
considere ms eficiente. Entre estas estrategias de asignacin de memoria se
encuentran:

Asignacin del primer ajuste.

Este criterio considera que es mejor asignar al proceso solicitante la primera


particin de memoria disponible cuyo tamao sea mayor o igual a los
requerimientos de espacio del proceso.

Asignacin de mejor ajuste.

170

Con este criterio se otorga al proceso solicitndole aquella particin libre que
mejor se ajuste a los requerimientos de memoria del proceso, es decir, el
menor bloque disponible de entre todos los que satisfaga las necesidades del
proceso.

Asignacin del pero ajuste.

Este criterio consiste en otorgar aquella particin mas grande que pueda
contener el proceso solicitante, el argumento que fundamenta este criterio es
que el bloque sobrante de esta particin puede ser utilizada para ser asignada
a otro proceso.
Otro aspecto importante que caracteriza a este modelo de administracin de
memoria es que tambin adolece de fragmentacin externa, esta ocurre en el
continuo ingreso y salida de procesos y de memoria, este fenmeno genera
que se produzca huecos libres en la memoria que individualmente no son
suficientes para contener a los procesos que requieren ejecutarse. En el pero
de los casos, existirn muchos pequeos huecos libres

esparcidos en la

memoria incapaz cada uno de alojar un proceso, pero unidos si podran


almacenarlo.
La falta de memoria, producto de la fragmentacin externa, se puede
recuperar llevando a cabo la compactacin de la memoria, es decir,
reagrupando los bloques de memoria hacia un lado y los huecos de memoria
libre hacia el otro extremo, de manera que se cree un nico bloque libre en un
extremo de la memoria de usuario. Esta tcnica requiere que algunos procesos
se desplacen dentro de la memoria, para esto es necesario que exista un
mecanismo de relocalizacin dinmica. Adems, la compactacin es muy
costosa en tiempo, pues requiere del movimiento de una gran cantidad de
informacin posiblemente a palabra. Generalmente, esta tcnica no se practica
en su forma, pues es un proceso demasiado lento.
A continuacin se estudiara algunos esquemas que se utilizan para mantener la
pista de la cantidad de memoria ocupada y disponible en un sistema.

Administracin de asignacin de memoria.

En los modelos vistos anteriormente, la unidad de asignacin de espacio es el


bloque, una porcin de memoria constituida por una secuencia de palabras
contiguas, caracterizada por su direccin inicial y su tamao. En particular,
171

describiremos los mtodos de administracin por mapa de bits, por listas


enlazadas y por sistema de asociados.
ADMINISTRACIN CON MAPA DE BITS.
Este esquema consiste en que la memoria se divide en una secuencia de
bloques de cierto tamao y se mantiene en memoria un arreglo de bits en
donde cada bit se utiliza par identificar el estado del bloque correspondiente en
la secuencia. Si un bloque esta ocupado, el bit correspondiente esta en 1, si el
bloque esta libre, el bit esta en 0.
Algo importante de mencionar es que si el tamao de asignacin es pequeo
entonces el mapa de bits es relativamente mas grande, a lo inversa, con un
bloque de asignacin mas grande el mapa de bits es mas pequeo. El tamao
del mapa de bits solamente esta determinando por el tamao de la memoria y
el tamao del
bloque es un compromiso, si el bloque es muy pequeo, el mapa de bits ser
muy grande y la reservacin de un bloque de memoria implicara el uso de
muchos bits.
ADMINISTRACIN DE MEMORIA CON LISTAS ENLAZADAS.
Este modelo puede ser utilizado cuando se asigna la memoria mediante
bloques de palabras contiguas. El control de memoria ocupada o libre del
sistema la mantiene mediante un lista enlazada donde los nodos son los
propios bloques. Cada bloque, adems de la informacin que almacena,
contiene informacin de administracin para indicar al sistema si esta libre u
ocupado y la direccin del bloque en la lista.
La lista podra estar ordenad por direcciones, la ventaja de este enfoque es que
la actualizacin de la lista es directa, pues cada nodo tiene dos vecinos, cada
uno de los cuales es un proceso o un hueco, luego al desocuparse un
segmento representado por un nodo en la lista pasa a ser u hueco, el cual
podra eventualmente agruparse a un hueco vecino. Este hecho muestra que
es mas til mantener una lista doblemente enlazada que una simplemente
enlazada.
La desventaja de este enfoque es que la bsqueda de un hueco libre
suficientemente grande para satisfacer una solicitud de un proceso es lenta.
172

Para solucionar este problema se puede tener dos listas enlazadas, una para
los huecos y otra para los procesos, sin embargo, esto obliga a mantener
estas dos listas actualizadas, es decir deben actualizarse ambas cuando un
proceso termina y cuando se otorga memoria a un proceso
A continuacin se presenta un ejemplo de cmo funciona este
mecanismo:

1
P

2
0

3 4 5 6
3

7 8

9 10 11 12 13 14 15 16 17 18 19 20

13 2

Este enfoque consiste en tener la memoria inicialmente como un gran hueco,


en principio de una longitud potencia de dos. A medida que un proceso la
requiera se particiona, siendo en todo momento una secuencia de bloques de
la longitud potencia de dos. Luego de una solicitud de un bloque, se busca el
hueco libre adecuado para satisfacer la solicitud, este hueco se divide a la
mitad sucesivamente hasta que se obtenga el bloque ms pequeo capaz de
satisfacer la solicitud. Suponga que la memoria es de 1 Mbytes, y un proceso
de 56 Kbytes ser cargado entonces la memoria se particiona en dos segmentos
de 512 Kbytes, estos segmentos siguen siendo muy grande para contener al
proceso entonces se vuelve a particionar uno de estos dos segmentos en dos
segmentos de 256 Kbytes, estos aun siguen siendo grandes, por lo que otra
vez se particiona uno de ellos, luego se tienen dos segmentos de 128 Kbytes,
los que siguen siendo grandes, se vuelve a particionar uno de ellos,
obtenindose dos bloques de 64 Kbytes. Como este tamao representa el
menor tamao potencia de dos que soporta el proceso que requiere memoria,
se le asigna al proceso uno de estos bloques. Este modelo introduce alguna
fragmentacin interna.

1.3 PAGINACIN.

173

La mayora de los sistemas que implementan memoria virtual, lo hacen


basados en el modelo de administracin de memoria por paginacin. La
paginacin es una tcnica que consiste en dividir el espacio de memoria virtual
en un conjunto de bloques de tamao fijo denominados paginas. El espacio de
direcciones virtuales de un proceso se considera un bloque de palabras
contiguas con direcciones relativas a 0, donde se agrupan los datos y cdigo
de un proceso. La memoria fija tambin es dividida en bloques de la misma
longitud que una pagina, llamados marcos de pagina. Cada pagina puede
almacenarse en un marco de pagina. La divisin de la memoria fsica en
marcos de paginas es auxiliar por el hardware.
Las ventajas de la paginacin es con respecto a los esquemas de
multiprogramacin con particiones fijas y variables, entre ellas estn:
1) No existe fragmentacin externa.
2) Los programas no necesitan ser almacenados en bloques de memoria
contiguos. Es decir, las unidades de asignacin de memoria son las
paginas y un proceso puede almacenarse en marcos de pagina dispersos
en la memoria, sin necesidad de que estn contiguos.
Pag 1.

Marco 1.

Pag 2.

Marco 2.

Pag 3.

Marco 3.

Pag 4.

Marco 4.

Pag 5.

Marco 5.

ESPACIO DE
DIRECCIONAMIENTO.
LGICO.

ESPACIO DE
DIRECCIONAMIENTO
REAL

El tamao de una pgina es definido por el hardware. Usualmente es una


potencia de dos entre 512 y 8192 palabras. El hecho de que esta longitud sea
potencia de dos hace mas fcil la traduccin en el sentido de que los dos
componentes de una direccin fsica se obtienen particionando la direccin

174

virtual. En general si P es la longitud de una pagina y V es una direccin


virtual, entonces

p=V div P representa la pagina del proceso a la que

pertenece esta direccin y adems la entrada de la tabla de pagina donde se


encuentra el marco de la pagina. Entonces si P=2r, el valor de d se encuentre
en los r bits menos significativos de V, los bits restantes constituirn el valor de
p.
Compartimiento en un sistema de paginacin pura
En los casos en que se usa hardware de almacenamiento a niveles mltiples,
el traspaso se hace por bloques, semejantes a las paginas. Esta tcnica se
conoce con el nombre de promocin o staging.
Cuando se usa la paginacin, las cadenas, los anillos y los ndices que forman
el mtodo de acceso fsico, podrn ajustarse a los tamaos de pagina o de
bloque de manera que se puedan minimizar estos traspasos o permutaciones.
El comportamiento del sistema, por ejemplo, se vera seriamente degradado si
existiesen cadenas que van de una pagina a otra para volver luego a la primera
o ir a una tercera.
A continuacin se describa brevemente un sistema de paginacin como los que
se utilizan los sistemas virtuales de hoy en da.
La siguiente figura muestra una memoria de dos niveles. Consiste en un
almacn de respaldo con capacidad para B2 bytes, en el cual se conservan
permanentemente los datos y una memoria principal de C1 bytes, en la que
residen temporalmente parte de los datos. Los datos se organizan en paginas
de longitud fija equivalente a B1 bytes. En el nivel 1, la memoria principal,
caben C1/B1 paginas. A su vez, el almacn de respaldo tiene cabida para
C2/B1 pginas. En ambos casos el numero de paginas es entero.

175

Cuando un programa pide datos, la probabilidad de encontrarlos en el nivel 1


ser P1. Esta probabilidad. Esta probabilidad se llama razn de acierto del
nivel 1. La probabilidad de encontrar esos datos en el nivel 2 ser P2, de modo
que:
P1 + P2 = 1.
El tiempo de acceso para el nivel 1 es t1, y el tiempo de acceso del nivel 2, t2.
Son stos tiempos de acceso totales, incluyndose en ellos el tiempo de
permanencia en las colas si es el del caso.
El tiempo medio de acceso para la memoria de dos niveles en conjunto ser,
entonces,
E(ta) = P1E (t1) + P2E (t2)
E (t2), el tiempo medio de acceso del nivel 2, puede ser ms de 1000 veces
mayor que el tiempo medio de acceso del nivel 1, E (t1), de modo que el tiempo
de acceso depende grandemente de P1.
Esta probabilidad depende de cuatro factores:
1. C1, la capacidad del nivel 1
2. B1, el tamao de pgina.
3. El flujo de reclamo de datos.
4. El algoritmo de reemplazo, es decir, la decisin acerca de la pgina que hay
que tachar en el nivel 1 cuando es necesario transferir a este nivel una pgina
nueva.

TABLAS DE PGINAS.

176

La MMU traduce las direcciones virtuales en reales con el apoyo de una tabla
de pginas. La tabla de paginas es un arreglo que es diseccionado por la
primera parte de la direccin virtual, dicho campo se usa como un ndice a la
tabla de paginas. Cada entrada en la tabla de pginas por lo menos en el
nmero de marco de pginas asociado a la pgina. Es frecuente que exista un
bit denominado Presente, que indica si la pgina se encuentra presente o no en
la memoria. Si el bit esta en 1, entonces la pagina tienen asociado un marco de
pagina en memoria. Este bit es til para implementar el mecanismo de memoria
virtual.
Las tablas de pginas usualmente se almacenan en memoria privilegiada. Por
lo general existe un registro de tabla de pgina que actualiza cuando se hace
activo un proceso y que contiene la direccin base de la tabla de pginas del
proceso. Esta direccin es parte del bloque de control de proceso y se guarda
en cada conmutacin de contexto. El contenido de este registro es utilizado por
MMU para traduccin de direcciones. Usualmente el hardware proporciona
registros asociativos o memorias cache de alta velocidad de acceso para
almacenar las entradas de tablas de pginas recientemente usadas.
Esto acelera los accesos a memorias, pues normalmente por cada acceso a
memoria de un programa, el hardware hace dos accesos, uno par ubicar el
marco dentro de la tabla de pginas y otro para acceder a la direccin
generada por el programa.
Cuando las pginas lineales resultan muy grandes, algunos sistemas
proporcionan una jerarqua de 2,3 o ms niveles de tablas de pginas.
Una entrada de una tabla de pginas contiene el nmero de marco de pgina
que almacena la pgina correspondiente de un proceso. Adems, es comn
que se almacenen otras informaciones para identificar el estado de la pgina.
Entre estos datos:

Bit Presente: indica si la pagina esta presente en memoria o no.

Bit de Proteccin: se refiere al tipo de acceso permitido sobre la pgina.


Estos pueden ser solo lectura, lectura/escritura, solo ejecucin, etc.

Bit Modificado: indica si la pgina ha sido modificada por alguna


instruccin recientemente.

Bit de Referencia: indica si la pgina ha sido referenciada por alguna


instruccin recientemente.
177

MEMORIA VIRTUAL. PAGINACIN CON DEMANDA.


La memoria virtual es una extensin del intercambio que permite que un
programa pueda ejecutarse sin estar cargado completamente en memoria. Es
decir, es posible que un programa se ejecute manteniendo solo una parte de
sus cdigos y datos en memoria en cada momento, permaneciendo el resto del
programa en el disco. En cambio, con el intercambio un programa puede
copiarse completamente en el disco para liberar memoria.
Entre los mecanismos de implementacin de memoria virtual, el mas usado, as
como el mas sencillo y eficiente es el de paginacin demandada. Este se
construye sobre algunas caractersticas adicionales del hardware usual de
paginacin. En la paginacin con demanda un programa tambin se divide
lgicamente en paginas de tamao fijo, de modo que un programa puede
mantener en memoria solo algunas paginas de su contenido y aun podra
ejecutarse.
En cada acceso que se realice a una pagina, el hardware chequeara si esta
presente o no a travs de este bit. Si durante algn acceso a pagina este bit
esta puesto a 0, el hardware generara una trampa. A esta condicin de
excepcin se le llama fallo de pgina.
Cuando ocurre un fallo de pgina se produce las siguientes acciones:
1) El sistema obtiene del disco la pgina faltante de la imagen del proceso.
2) Se selecciona un marco de pgina libre.
3) Se copia la pgina en el marco.
4) Se actualiza la tabla de paginas del proceso(se pone el bit Presente a 1)
5) Se recomienda la instruccin que produjo el fallo de pgina.
Todo estos mecanismos es transparente al usuario, es decir, este mecanismo
no impone ningn tipo de restriccin al programador. El usuario de un proceso
solo vera mas operaciones de entrada/salida que lo normal.
ALGORITMO DE REEMPLAZO DE PGINAS.
Cuando las instrucciones que se ejecutan hacen referencia a pginas que no
estn presentes en memoria, se debe buscar marcos libres para almacenarlas.

178

Sin embargo, puede ocurrir que el sistema no posea marcos de pginas libres
o bien el nmero de marcos libres sea inferior a los exigidos para el buen
funcionamiento del sistema. En este caso el sistema debe ejecutar alguna
accin que le permita liberar marcos de pginas para asignrselos a las
pginas que lo necesitan. Las pginas que tienen asociados marcos de
pginas que se elijan como donadoras de marcos deben ser guardadas en
disco. De aqu es necesario que el sistema implemente estrategia eficiente que
le permitan reemplazar pginas de

memoria con el fin de utilizar mejores

criterios para el reemplazo de pginas. A continuacin se presentan algunos de


ellos:
ALGORITMO PTIMO (OPT).
Consiste en elegir como victima la pagina que ser usada en el mayor tiempo,
o sea, aquella que esperara mas tiempo en memoria para ser referenciada.
Con esta idea se consigue una razn de fallos de paginas optima, adems de
otras ventajas, la principal dificultad es que es prcticamente imposible conocer
cual ser esa pagina, esto implica contar con algn conocimiento del futuro.
En un sistema esto debera ser imposible, a menos que los procedimientos
brinden esa informacin adicional. Lo normal es que cuando ocurre un fallo de
pagina no se conozca la prxima pagina que se referenciada.
Una situacin parecida ocurre con el algoritmo SJF para planificacin de la
CPU. En la prctica se implementan aproximaciones a OPT.
ALGORITMO LRU (LEAST RECENTLY USED O MENOS RECIENTE
USADA)
Este algoritmo es una aproximacin a OPT, que trata de obtener informacin
acerca del uso futuro de las pginas reciente. La idea de este algoritmo
consiste en que es muy probable que una pagina que no ha sido usada por
mucho tiempo, no sea usada en el futuro cercan. Segn este algoritmo, se elige
como pagina victima a la pgina menos recientemente usada, la que fue
referenciada en mayor tiempo.
La implementacin de este algoritmo por lo general es cara. Una forma de
llevarla a cabo es manteniendo una lista enlazada con todas las paginas que
tienen asociado un marco de pagina, la lista debe estar ordenada de tal manera
179

que la primera pagina de la lista sea la de uso mas reciente y la ultima sea la
de uso menos reciente. Entonces cuado una pagina es referenciada se le
coloca en la cabecera de la lista. El principal problema de este esquema
consiste en que la lista debe ser actualizada continuamente despus de cada
referencia a memoria y esto conlleva a demasiada sobrecarga en el sistema.
Otras variantes de implementacin pueden ser proporcionadas por el hardware.
Una de estas soluciones consiste en tener un contador por paginas (podra
estar incorporado a una entrada en la tabla de paginas) el cual se incrementa
despus que la pagina es referenciada. Luego, cuado ocurre un fallo de pagina
se elige aquella pagina que posea el contador con menor valor numrico.
LRU es un algoritmo de muy buenos resultados que ha sido muy usado en la
prctica.
Tambin son conocidas algunas aproximaciones a LRU como LFU y otros.

CLASE DE REFERENCIA.
Otra solucin al problema de seleccionar una pagina victima y de fcil
implementacin, se apoya en el uso de los bits de referencia y modificado para
caracterizar 4 clases de referencia a paginas. Este algoritmo consiste en
reemplazar aquellas pginas que no han sido utilizadas ltimamente.
El bit de referencia es activado cuando se hace referencia la pgina y el bit
modificado es activado cuando la pgina es modificada. El bit de referencia es
desactivado peridicamente para poder diferenciar las pginas de uso reciente
de las de uso no tan reciente.
La definicin de las clases es la siguiente:

Clase 0: R=0 y M=0, no se ha hecho referencia reciente a la pagina ni ha


sido modificada.

Clase 1: R=0 y M=1, no se ha hecho referencia a la pagina


recientemente, pero si ha sido modificada.

Clase 2: R=1 y M=0, se ha hecho referencia recientemente a la pagina,


pero no se ha modificado.

180

Clase 3: R=1 y M=1, se ha referenciado recientemente y se ha


modificado la pagina.

Cuando ocurre un fallo de pgina, el algoritmo selecciona como victima una


pgina de la clase con menor numero asociado no vaca. Si existen varias
paginas en la clase, se elige solo una mediante algn criterio de seleccin.

Algoritmo FIFO.
Como su nombre sugiere, la idea del algoritmo es seleccionar como pagina
victima a la pgina que tenga ms tiempo de carga en memoria. Este algoritmo
es de fcil implementacin pero de un desempeo inestable. Una posible
implementacin puede ser de llevar a cabo mediante el uso de una lista que
enlace todas las pginas que estn en memoria. Dicha lista estar ordenada
segn el orden de cargadas, es decir, la primera pagina de la lista es la mas
antigua y la ultima la mas recientemente cargada.
Cuando ocurre un fallo de pagina se elige otra como victima la primera pagina
de la lista, uno de los inconvenientes de este algoritmo es que no considera la
importancia de las paginas ni con que frecuencia se estn utilizando.
Es posible que la primera pgina pertenezca un bloque de rutinas de uso
comn. En tal caso la razn de fallos de pginas puede ser muy alta.
ALGORITMO DE LA SEGUNDA OPORTUNIDAD
Este algoritmo introduce una mejora al algoritmo FIFO, se apoya en el uso del
bit de referencia de las pginas. Con esta mejora se toma en cuenta la
frecuencia del uso de las pginas en alguna medida.
El algoritmo acta de la siguiente manera: cuando ocurre un fallo de pgina se
revisa la lista de pginas desde la mas antigua hasta la mas nueva. Si el bit de
referencia de la pgina ms antigua es 0, entonces se reemplaza esta pgina
pero si este bit es 1, entonces se coloca el bit de referencia en 0 y se coloca la
pagina al final de la lista, luego se consulta la siguiente pgina mas antigua y
as sucesivamente.
A este algoritmo se le llama de segunda oportunidad porque se le da a cada
pgina una segunda oportunidad de quedarse en memoria si es que ha sido

181

referenciada. El problema de este algoritmo es que es de desempeo lento,


pues la lista debe modificarse, es decir se debe sacar un nodo de un sitio para
ser insertado en otro lugar, adems de que es inestable.
ALGORITMO DEL RELOJ.
Este algoritmo bsicamente es igual al de segunda oportunidad, solo que con
una variacin en la estructura de la lista. En este caso se tiene una lista
circular, donde existen un puntero que apunta ala pgina ms antigua, luego,
cuando ocurre un fallo de pgina se consulta el valor del bit de referencia de la
pgina, si es 0 entonces esa pgina se elige para el reemplazo, en cambio si el
bit es 1 entonces el bit se pone en 0 y se incrementa el puntero de manera que
apunte al siguiente nodo de la lista que corresponde a la siguiente pgina ms
antigua.

ALGORITMO DEL RELOJ CON DOS MANECILLAS.


Este algoritmo es igual al anterior, solo que existen dos punteros, un puntero va
delante otro, el primer puntero verifica el bit de referencia de la pgina, si este
es igual a 1 se pone en 0, luego cuando pase el segundo puntero verifica el
estado del bit de referencia, si es 0 entonces intercambia la pgina, sino lo
pone en 0 y apunta a la siguiente pgina. El objeto de este algoritmo es
privilegiar a aquellas pginas mas referenciadas para que se queden en
memoria.
ASPECTO DE DISEO.
Modelos de conjuntos de trabajo: Segn el esquema de paginacin con
demanda pura, cuando un programa se ejecuta las pginas que contienen las
instrucciones que se ejecutan se van cargando en memoria a medida que se
requieran, producindose por lo tanto, fallos de paginacin como pginas se
quieran cargar en memoria.
182

Por lo general, los procesos no requieren tener todas sus instrucciones ni datos
en memoria sino que tanto los datos como las instrucciones se necesitan por
ciertos periodos de tiempo. Esto significa que por lo general, en un periodo de
tiempo, el proceso solo requiere un conjunto de sus pginas en memoria.
A esta caracterstica de los procesos se le denomina localidad de referencia.
Al conjunto de pginas que requieren un proceso en un intervalo de tiempo de
su ejecucin se le conoce como conjunto de trabajo. Lgicamente, si un
proceso tiene su conjunto de trabajo en memoria determinado momento,
entonces el proceso no genera fallos de pginas.
Dadas las caractersticas de los conjuntos de trabajo t de las ventajas del
conocimiento de los conjuntos de trabajo de un proceso, muchos sistemas
intentan llevar un registro de los conjuntos de trabajo de los procesos de tal
manera que la siguiente vez que se ejecute el proceso, las paginas asociadas a
sus conjuntos de trabajo puedan ser cargadas antes de que se necesiten. A
este mecanismo se le conoce como prepaginacin.

Por otro lado, el concepto de conjunto de trabajo puede ser utilizado por un
algoritmo de reemplazo de pginas, pues adems de ejecutar el criterio de
seleccin implcito en el algoritmo se puede ver si la pagina seleccionada
pertenece o no al conjunto de trabajo, si pertenecen entonces la pgina no se
reemplaza.
Tamao de la paginacin:

El tamao de la pgina es un aspecto de diseo

de consideracin. Si la pgina es muy pequea se necesita utilizar una gran


cantidad de recursos del sistema para poder administrarlas, adems de que
cada proceso requerira de ms pginas para almacenarse en memoria. Por
otro lado, si la paginacin es muy grande se pierde mas memoria por concepto
de fragmentacin interna.
El tamao optimo debe calcularse considerando los aspectos mencionados,
mas el tamao promedio de los procesos que se ejecutan en el sistema.
Paginas compartidas: Otros aspectos de diseo importante de considerar se
referencia a la comparticin de paginas. En un sistema multiusuario y
multitarea es frecuente que distintos usuarios ejecuten una misma aplicacin
simultneamente. Incluso eso es frecuente en un sistema multitarea para un
183

usuario, es decir, donde las tareas que no son lanzadas por el sistema, son
lanzadas por el usuario que esta operando el sistema. Puede ocurrir que
existan varias copias de texto de un programa del cual estn cargadas varias
instancias en memoria. Esta situacin puede conllevar a un gasto innecesario
de espacio de memoria.
En estas condiciones el sistema debera permitir compartir las pginas
asociadas al cdigo de la aplicacin. En general, texto de un programa no
debera automatizarse, para evitar conflictos entre las diferentes instancias. Por
ejemplo, con el CPU 386 los segmentos de cdigo de los programas se
marcan, por lo general, de solo ejecucin.
Suponga, por ejemplo, que dos usuarios, P y R, de un sistema, estn usando el
mismo editor de texto, el sistema debera marcar como compartidas las
paginas que estn relacionadas con el cdigo ejecutable de la aplicacin y las
paginas de datos del editor de cada uno de los usuarios

deberan ser

particulares a cada uno de ellos. La razn esta en que el contenido de estas


ultimas si puede modificarse.

1.5 SEGMENTACION.
Uno de los problemas fundamentales de la paginacin es que no se adecua a
la visin que el usurario tiene de sus programas y de la memoria real. Con la
paginacin, el espacio de direcciones virtuales de un programa es un bloque de
localizaciones consecutivas cuya direccin inicial relativa es 0. En ese espacio
se agrupan todos los elementos del programa: variables, pila y texto. En
general, este esquema ofrece muchas ventajas al desarrollador del sistema, la
implementacin es sencilla, proporciona un mecanismo de relocalizacin
dinmica y permite implementar el mecanismo de memoria virtual de una
manera limpia y sencilla, pero se aleja de la perspectiva del usuario con
respecto al programa y a la forma en que se almacena el programa en
memoria.
La visin que tiene un usuario con respecto a un programa toma en cuenta que
un programa es una coleccin de varios elementos funcionalmente distintos.
Entre estos elementos se cuentan, en general, los datos, las pilas y las
entidades de cdigo. Dividir un programe en partes de igual tamao no es

184

natural. Normalmente, los lenguajes estructurados, como Pascal y C, permiten


la creacin de entidades lgicas como procedimientos y mdulos de diferentes
tamaos. Una entidad lgica puede ser manipulada por el mecanismo
administrador de memoria, para almacenar la informacin de la entidad en un
bloque de memoria llamado segmento.
Por lo general los usuarios tienden a pensar que los datos se almacenan en
algn lugar determinado, los procedimientos y funciones en otro lugar, la pila en
otro lugar y el programa principal en otro lugar.
De esta visin surge la tcnica denominada segmentacin. Con esta tcnica
en un sistema de administracin de memoria se distinguen diferentes espacios
de direccionamiento, los cuales reciben el nombre de segmentos. Cada
segmento tiene una secuencia lineal de direcciones y tiene un tamao
determinado. Una de las principales caractersticas de los segmentos es que
son de longitud variable, es decir que pueden crecer o decrecer en tamao, a
diferencia de las pginas.
Dado que los segmentos son espacios de direccin independientes uno de
otros, cada uno de ellos debe crecer en forma independiente sin afectar ni ser
afectado por los dems. La implementacin de la segmentacin, apoyada en el
hardware, debe proporcionar esta facilidad.
La implementacin usual de la segmentacin concibe a cada proceso como
una coleccin de segmentos, cada uno identificado por la direccin inicial en la
memoria, la longitud y alguna otra informacin de control. Entonces, el espacio
de direcciones virtuales de un proceso esta formado por un conjunto de
bloques de localizaciones consecutivas de memoria, cada uno con direccin
inicial relativa 0. Cada bloque tendr adems una identificacin, un nombre o
un nmero. Por lo general, los segmentos se identifica por un numero
perteneciente a la sucesin 0, 1,2...., n-1, donde n es el numero de segmentos
del proceso.
Una direccin virtual esta formada por dos elementos, el nmero de segmento
y la direccin de acceso (desplazamiento) dentro del segmento. A cada proceso
se le asigna una tabla de segmentos, que contiene la informacin de
identificacin de los segmentos del proceso, incluyendo informacin de
proteccin y de control. Adems esta tabla es usada por el hardware, en
185

particular por la MMU de la CPU, para traducir las direcciones virtuales


generadas por los procesos en direcciones fsicas. La tabla de segmentos es
usualmente un arreglo de bloques estructurados. Cada entrada de este arreglo
contiene algunos de los siguientes campos:

Direccin fsica de inicio del segmento (direccin base).

Longitud del segmento (limite).

Bits de control de acceso (de lectura, escritura, ejecucin, etc.).

Bit Presente.

Bits de referencia/Modificado.

Cuando una direccin virtual (formada por dos valores, el numero de segmento
y el desplazamiento dentro del segmento) es generada por un programa, la
MMU obtiene la entrada de la tabla de segmentos cuyo ndice coincide con el
numero de segmento, verifica que la operacin que se realizara es permitida,
de lo contrario se produce una trampa al sistema, compara el desplazamiento
con el limite, si es mayor o igual se produce una direccin base del segmento
para producir la direccin fsica correspondiente.

SEGMENTACIN PURA
La implantacin de la segmentacin difiere del caso de la paginacin en un
sentido esencial: las pginas tienen un tamao fijo y los segmentos no. La
figura 14 muestra un ejemplo de memoria fsica que contiene al principio 5
segmentos. Consideremos que el segmento 1 se elimina y su lugar se ocupa
por el segmento 7, que es menor. El rea que queda entre el segmento 7 y el 2
es un hueco. Luego, el segmento 4 se reemplaza por el segmento 5 y el
segmento 3 es reemplazado por el segmento 6. Despus de que el sistema
est en ejecucin durante cierto tiempo, la memoria quedar dividida en varios
bloques, algunos con segmentos y otros con huecos.
Comparacin de paginacin y segmentacin.
Considerando
Necesita
programador

saber
si

el

Paginacin

Segmentacin

No

est

186

utilizando esta tcnica?


Cuntos espacios lineales

Muchos

No

No

No

de direcciones existen?
Puede el espacio total de
direcciones

exceder

tamao

la

de

el

memoria

fsica?
Pueden

distinguirse

los

procedimientos y los datos,


adems de protegerse en
forma independiente?
Pueden
facilidad

adecuarse
las

tablas

con
con

tamaos fluctuantes?
Se

facilita

el

uso

de

procedimientos compartidos
entre los usuarios?
Para qu se invent esta Para
tcnica?

obtener

un Para

permitir

que

los

gran espacio lineal programas y datos fueran


de direcciones sin separados

en

espacios

tener que adquirir independientes


ms

memoria direcciones

fsica

de
y

poder

proporcionar la proteccin y
uso de objetos compartidos

SEGMENTACIN CON PAGINACIN.

187

Para resolver el problema de la fragmentacin externa, manteniendo el


esquema de la segmentacin se han propuesto e implementado otros modelos
como la segmentacin con paginacin. En este esquema se mezclan las
bondades de las tcnicas de segmentacin y paginacin. Este tipo de
administracin contempla la divisin del espacio de direccionamiento lgico en
un conjunto de segmentos, en donde cada segmento se divide lgicamente en
pginas. Cada proceso tiene su propia tabla del segmento y cada segmento
esta asociado con una tabla de pginas. La memoria fsica esta particionada en
marcos de pginas.
Una direccin virtual en este modelo, al igual que en la segmentacin, esta
formada por dos elementos, un nmero segmento y el desplazamiento dentro
del segmento.
La direccin base en la entrada correspondiente de la tabla de segmentos
apunta al inicio de la tabla de paginas base en la entrada correspondiente de la
tabla de segmento apunta al inicio de la tabla de paginas de segmentos. El
desplazamiento es divido a su vez en dos elementos, un numero de paginas y
el desplazamiento dentro de la pagina. La direccin fsica se construye
sumando a la direccin inicial del marco de pgina cuyo nmero se encuentra
en la entrada correspondiente en la tabla de pginas, el desplazamiento.
La combinacin de estos dos esquemas ha sido adoptada por muchos
microprocesadores, entre ellos el procesador utilizado en la GE-45 donde se
ejecuto Multics y el 80386 y superiores de Intel.

PAGINACIN POR DEMANDA.


Es similar a lo visto para la paginacin introduciendo el concepto de swapping.
Los procesos residen en el disco y al ser ejecutados deben ser cargados en
memoria. Cuando un proceso va a ser ejecutado, el mismo es swappeado a
memoria, utilizando lazy swapping. El lazy swapping nunca trae paginas a
memoria si no van a ser ejecutadas. Se necesita determinar si un pagina esta
en memoria o en disco, por lo cual se utiliza el bit de vlido / invlido de la tabla
de pginas. Si el bit = 1 la pgina es valida y esta cargada en memoria si es 0
la pgina es invlida y no esta cargada en memoria (esta en disco).

188

Cuando un proceso intenta acceder a una pgina que no esta cargada en


memoria ocurre un page fault (tomo de pgina). El procedimiento para manejar
un page fault es el siguiente:
1. Verificar si la referencia a la pagina es valida (se utiliza una tabla interna
(generalmente llevada en PCB) donde se indica las paginas validas.
2. Si la referencia no es valida, se cancela la ejecucin del proceso.
3. Encontrar un frame disponible para cargarla (la pgina esta en disco)
(por ejemplo de la free frame list)
4. Solicitar operacin de I/O para leer la pgina de disco cargarla en el
frame obtenido.
5. Modificar la tabla interna y la tabla de paginas para que ahora esta
pagina figure como que esta en memoria.
6. Continuar con la ejecucin del proceso en la instruccin en la que fue
interrumpido.

MANEJO DINAMICO DE MEMORIA


En la memoria dinmica se asigna y libera un nodo. Hay dos caractersticas de
lo nodos que hacen de los mtodos previos tcnicos adecuadas:

La primera es que cada nodo de un tipo dado es de tamao fijo.

La segunda es que el tamao de cada nodo es bastante pequeo.

En algunas aplicaciones, sin embargo, esta caractersticas no e mantiene. Un


programa en particular podra requerir un gran cantidad de memoria continua
(por ejemplo, un arreglo muy grande). Seria poco practico intentar obtener tal
bloque nodo a nodo. De manera similar, un programa puede requerir bloques
de mamara

de una gran variedad de tamaos. En casos como esos un

sistema de manejo de memoria tiene que ser capaz de procesar peticiones de


bloques de longitud variable.
Ejemplo. Considere una memoria pequea de 1024 palabras. Suponga que se
hace una peticin de tres bloques de memoria de 348, 110 y 212 palabras.
Adems, supongamos que estos bloques se asignan de manera
secuencial

189

348

458

670

1023

Bloque 1

Bloque 2

Bloque 3

Espacio libre

(Tamao = 348)

(Tamao = 110)

(Tamao = 212)

(Tamao = 354)

Supongamos ahora que se libera el segundo bloque de tamao 110,


dando como resultado lo siguiente.

348

458

670

Bloque 1

Espacio libre

Bloque 3

Espacio libre

(Tamao =348)

(Tamao =110)

(Tamao =212)

(Tamao =354)

Hay ahora 464 palabras de espacio libre; no obstante, dado que el espacio libre
esta dividido en bloques no continuos, una peticin de un bloque de 400
palabras no podra ser satisfecha. Suponiendo que el bloque numero tres fuese
liberado ahora. Es claro que no se desea retener tres bloques libres de
110,212, y 354 palabras. En lugar de ellos se deber combinar los bloques en
un solo bloque grande de 676 palabras de manera que pueda ser satisfechas
peticiones grandes posteriores.
BLOQUE 1

ESPACIO LIBRE

Tamao =348

(Tamao = 676)

Este ejemplo ilustra la necesidad de no perder de vista el espacio


para signar porciones de ese espacio cuando se

disponible,

presente peticiones de

asignacin y combinar espacios libres contiguos cuando se librera un bloque.

190

CAPACITACIN DE BLOQUES DE MEMORIA


Un esquema usado a veces necesita implica compactacin de
memoria de la siguiente manera:
Al inicio la memoria es un gran bloque de memoria disponible. Cuando llegan
peticiones de memoria, los bloques de memoria se asignan de manera
secuencial comenzando por la primera localidad de memoria.

Asignand

125

Libre

325

500

Asignand

(1)

(2)

750

Libre

800

850

950

Asig.

Asig.

Asig.

(3)

(4)

(5)

1023

Libre

Antes de la compactacin
Una variable freepoint contiene la direccin de la primera localidad que sigue al
ltimo nodo asignado. Freepoint es igual a 950 note que todas la localidades de
memoria entre freepoint y la direccin mayor de la memoria esta libre. Cuando
se libre un bloque, freepoint permanece
Intacta y no ocurren combinaciones de espacios libres. Cuando se asigna un
bloque de tamao, freepoint se incrementa en n. Estos continua hasta que se
pida un bloque de tamao n y freepoint +n-1 sea mayor que la direccin mayor
de la memoria.
La peticin no puede ser satisfecha sin que ocurra algo antes.
En ese momento se interrumpen las rutinas de los usuarios y se llama a una
rutina de compactacin. Una rutina de este tipo copia todos los nodos

191

asignados en localidades de memoria secuencial comenzando por la direccin


ms pequea de la memoria. As, todos
Los bloques libres que estn intercalados entre bloques asignados se eliminan,
freepint se hace igual a la suma de losa tamaos de todos los bloques
asignados. Se crea un bloque grande en el externo superior de la memoria y la
peticin del usuario puede ser satisfecha
Si hay suficiente memoria disponible.
0

Asig.(1)

125

300

350

400

Asig.(3)

Asig.(4)

Asig.(5)

Asig.(2)

500

Libre

Despus de la compactacin

Freepoint=500
Cuando se copia los bloques asignados en porciones mas pequeas, hay que
tener especial cuidado
Para que los apuntadores mantengan sus valores correctos.
As, con el objeto de que la compactacin sea exitosa, tiene que haber un
mtodo para determinar si los contenidos de una localidad dada son
direcciones.
Una alternativa es un sistema que calcule direcciones como desplazamiento de
alguna direccin base. En ese caso solo tiene que ser cambiados los
contenidos de esa direccin base y el desplazamiento en la memoria en la
memoria no necesita ser alternado.
Una rutina de compactacin requiere de un mtodo mediante el cual puede ser
determinado el tamao de un bloque y su estado (asignado o libre).
La compactacin es similar a la recoleccin de basura en que el
procesamiento de todos los usuarios de detiene cuando el sistema toma
el tiempo para limpiar su memoria. Por

esta razn, y a causa del

problema del apuntador discutido antes se usa la compactacin de


manera tan frecuente para la asignacin de nuevos bloques en memoria.

192

Supongamos que antes de la compactacin se solicita una peticin de un


bloque de 150 palabras antes de asignarle memoria a esta peticin hay que
compactar los bloques que estn siendo ocupados. Esto con el fin:
Evitar que no exista suficiente espacio en memoria para la peticin.
Evitar que los bloques libres que se encuentran entre bloques asignados sean
muy pequeos.
Evitar la de fragmentacin externa.
Ahora una vez compactados los bloques se prosigue a la solicitud y se asigna
un bloque de memoria a la peticin si

es que existe aun as memoria

disponible para la peticin.


0

125

300

350

400

500

650

1
Asignado Asignado Asignado Asignado Asignado Asignado Libre
(1)

(2)

(3)

(4)

(5)

(6)

MANEJO DINAMICO DE MEMORIA


En la seccin previa supusimos que en la memoria sea signa y libera un nodo
cada vez. Hay dos caractersticas de los nodos que hacen de los mtodos
previos tcnicas adecuadas. La primera es que cada nodo de un tipo dado es
de tamao de cada nodo es bastante pequeo. En algunas aplicaciones, sin
embargo, estas caractersticas no se mantienen. Por ejemplo un programa
particular

podra requerir una gran

cantidad

de memoria

contigua (por

ejemplo, un arreglo muy grande). Seria poco practico interna obtener

tal

bloque nodo a nodo.


De manera similar, un programa puede requerir bloques de mamaria de una
gran variedad de tamaos como esos un sistema de manejo de memoria tiene
que ser capaz de procesar peticiones de bloque de longitud variable. En esta
seccin discutimos algunos sistemas de este tipo.

193

RESERVACION Y CAPTACION DE MEMORIA.


Las variables dinmicas se manejan en el montculo, y deben su nombre al
hecho de que pueden ser creadas y destruidas durante el tiempo de
ejecucin

de

un

mdulo.

Para el manejo de variables dinmicas se hace indispensable la


utilizacin de apuntadores, as como de funciones especiales para la
asignacin y liberacin de la memoria correspondiente a dichas
variables.
Tanto en C como en C++ existen funciones tales como malloc() y free()
para la asignacin y liberacin de memoria del montculo.
Adems de estas funciones, el C++ cuenta con dos operadores
interconstruidos:
new

que realiza una labor parecida a la de la funcin malloc(),

.asignando un bloque de memoria.


delete que libera un bloque de memoria asignada en tiempo de
ejecucin, de manera semejante a como lo hace free().
En el listado 6.4 se muestra un ejemplo de aplicacin de los operadores new y
delete.
#include <iostream.h>
void main()
{
int *apent;

// Declara un apuntador a entero

apent = new int ; // Reserva un bloque de memoria dinmica


// de 2 bytes para manejarlo por medio
// de apent.
*apent = 10 ;

// Asigna el valor 10 al objeto apuntado

// por apent.
cout << *apent ; // Despliega el contenido del objeto // apuntado por
apent. delete apent ; // Libera el espacio de memoria manejado // por
apent. }

194

Listado

6.4.-

Aplicacin

de

new

delete.

En el programa del listado 6.4, se supone que la reservacin ser


exitosa

porque

existe

espacio

suficiente

en

el

montculo.

Pero quin asegura que el espacio requerido por new est


disponible?. Para controlar esta situacin y evitar un mensaje de error
por parte del sistema en tiempo de ejecucin, en 6.5 se propone una
nueva versin del programa.
#include <iostream.h>
#include <stdlib.h> // Para exit().
void main()
{
int *apent; // Declara un apuntador a entero
if((apent = new int)==NULL)// Intenta reservar un bloque de
{ // memoria dinmica de 2 bytes ra
// manejarlo por medio de apent.
cout << "NO hay espacio suficiente\n"; exit(1); // Finaliza la ejecucin
del programa. } *apent="10" ; // Asigna el valor 10 al objeto apuntado //
por apent. cout << *apent ; // Despliega el contenido del objeto //
apuntado por apent. delete apent ; // Libera el espacio de memoria
manejado // por apent. }
Listado

6.5.-

Reservacin

de

memoria

para

una

variable

dinmica.

Los operadores new y delete pertenecen al Lenguaje C++ , de tal


manera que no se requiere incluir ningn archivo de cabecera para
utilizarlos.
Para crear un arreglo de 25 elementos de tipo double, en el montculo,
puede escribirse:
double* dap ;
dap = new double[25];
su forma equivalente:
double* dap = new double[25];

195

En este ejemplo, se est declarando a dap como un apuntador a variables


dinmicas

de

tipo

doble;

al

tiempo

que

se

le

asigna

el valor retornado por new. El valor retornado por new es la direccin


del inicio de un bloque de memoria del tamao requerido para
almacenar

25

elementos

de

tipo

double.

En caso de que el montculo no disponga del espacio requerido, new


retorna

el

valor

NULL

nulo

).

Aunque, cuando se requiere memoria a travs de new se debe indicar


el tamao exacto del bloque requerido, el tamao del bloque se puede
dar en tiempo de ejecucin como se muestra en el listado 6.6.
#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
void main()
{
unsigned int n;
clrscr();
gotoxy(20,1);
cout << "NUMERO DE ELEMENTOS DEL ARREGLO: "; cin>> n;
float* apf;
if((apf=new float[n])==NULL)
{
cout << "NO HAY ESPACIO SUFICIENTE EN EL MONTICULO\N";
exit(1); }

for(int x="0" ; x < n ; x++) { gotoxy(20,x+3);

*(apf+x)="x+100.25;" cout << *(apf+x) << "\n"; } delete apf; getch(); }


Li

Fijacin de variables apuntadas


Aunque un puntero slo puede apuntar a datos de tipos que puedan
almacenarse completamente en pila (o sea, que no sean ni objetos de tipos
referencia ni estructuras con miembros de tipos referencia), nada garantiza que

196

los objetos apuntado en cada momento estn almacenados en pila. Por


ejemplo, las variables estticas de tipo int o los elementos de una tabla de tipo
int se almacenan en memoria dinmica an cuando son objetos a los que se
les puede apuntar con punteros.
Si un puntero almacena la direccin de un objeto almacenado en memoria
dinmica y el recolector de basura cambia al objeto de posicin tras una
compactacin de memoria resultante de una recoleccin, el valor almacenado
en el puntero dejar de ser vlido. Para evitar que esto ocurra se puede usar la
instruccin fixed, cuya sintaxis de uso es:
fixed(<tipo> <declaraciones>)
<instrucciones>
El significado de esta instruccin es el siguiente: se asegura que durante la
ejecucin del bloque de <instrucciones> indicado el recolector de basura nunca
cambie la direccin de ninguno de los objetos apuntados por los punteros de
tipo <tipo> declarados. Estas <declaraciones> siempre han de incluir una
especificacin de valor inicial para cada puntero declarado, y si se declaran
varios se han de separar con comas.

Los punteros declarados en <declaraciones> slo existirn dentro de


<instrucciones>, y al salir de dicho bloque se destruirn. Adems, si se les
indica como valor inicial una tabla o cadena que valga null saltar una
NullReferenceException. Tambin hay que sealar que aunque slo pueden
declarase punteros de un mismo tipo en cada fixed, se puede simular
facilmente la declaracin de punteros de distintos tipos anidando varios fixed.
Por otro lado, los punteros declarados en <declaraciones> son de slo lectura,
ya que si no podra cambirseles su valor por el de una direccin de memoria
no fijada y conducir ello a errores difciles de detectar.

197

Un uso frecuente de fixed consiste en apuntar a objetos de tipos para los que
se puedan declarar punteros pero que estn almacenados en tablas, ya que
ello no se puede hacer directamente debido a que las tablas se almacenan en
memoria dinmica. Por ejemplo, copiar usando punteros una tabla de 100
elementos de tipo int en otra se hara as:
class CopiaInsegura
{
public unsafe static void Main()
{
int[] tOrigen = new int[100];
int[] tDestino = new int[100];
fixed (int * pOrigen=tOrigen, pDestino=tDestino)
{
for (int i=0; i<100; i++)
pOrigen[i] = pDestino[i];
}

Como puede deducirse del ejemplo, cuando se inicializa un puntero con una
tabla, la direccin almacenada en el puntero en la zona <declaraciones> del
fixed es la del primer elemento de la tabla (tambin podra haberse hecho
pOrigen = &tOrigen[0]), y luego es posible usar la aritmtica de punteros para
acceder al resto de elementos a partir de la direccin del primero ya que stos
se almacenan consecutivamente.
Al igual que tablas, tambin puede usarse fixed para recorrer cadenas. En este
caso lo que hay que hacer es inicializar un puntero de tipo char * con la
direccin del primer carcter de la cadena a la que se desee que apunte tal y
198

como muestra este ejemplo en el que se cambia el contenido de una cadena


Hola por XXXX:
class CadenaInsegura
{
public unsafe static void Main()
{
string s=Hola
Console.WriteLine(Cadena inicial: {0}, s);
fixed (char * ps=s)
{
for (int i=0;i<s.Length;i++)
ps[i] = A;
}
Console.WriteLine(Cadena final: {0}, s);
}

La salida por pantalla de este ltimo programa es: Hola


La ventaja de modificar la cadena mediante punteros es sin ellos no sera
posible hacerlo ya que el indizador definido para los objetos string es de slo
lectura.
Cuando se modifiquen cadenas mediante punteros hay que tener en cuenta
que, aunque para facilitar la comunicacin con cdigo no gestionado escrito en

199

C o C++ las cadenas en C# tambin acaban en el carcter \0, no se


recomienda confiar en ello al recorrerlas con punteros porque \0 tambin
puede usarse como carcter de la cadena. Por ello, es mejor hacer como en el
ejemplo y detectar su final a travs de su propiedad Length.
Hay que sealar que como fixed provoca que no pueda cambiarse de direccin
a ciertos objetos almacenados en memoria dinmica, ello puede producir la
generacin de huecos en memoria dinmica, lo que tiene dos efectos muy
negativos:

El recolector de basura est optimizado para trabajar con memoria


compactada, pues si todos los objetos se almacenan consecutivamente
en memoria dinmica crear uno nuevo es tan sencillo como aadirlo tras
el ltimo. Sin embargo, fixed rompe esta consecutividad y la creacin de
objetos en memoria dinmica dentro de este tipo de instrucciones es
ms lenta porque hay que buscar huecos libres.

Por defecto, al eliminarse objetos de memoria durante una recoleccin


de basura se compacta la memoria que queda ocupada para que todos
los objetos se almacenen en memoria dinmica. Hacer esto dentro de
sentencias fixed es ms lento porque hay que tener en cuenta si cada
objeto se puede o no mover.

Por estas razones es conveniente que el contenido del bloque de instrucciones


de una sentencia fixed sea el mnimo posible, para que as el fixed se ejecute lo
antes posible.

LISTAS GENERALES
CONCEPTO:
Es un tipo de dato abstracto con una secuencia simple de objetos llamados
Elementos, asociados a cada elemento de la lista hay un valor,
Caracterstica de las listas generales:
200

1- Puede contener cualquier tipo de dato (entero, caracteres, listas).


Ejemplo: (5, A, B, 3, (2,B) ).
2- Tienen un apuntador externo list1.
Ejemplo: list1=(5, 12, s, 147, a ).
3- Los parntesis ( ) representa lista, tambin puede ser que la lista este
vaca.

Operaciones Abstracta
1- La operacin head (list2) , que se va implantar a lista generales , se
implanta solamente a la lista que no este vaca. Al primer elemento le
damos el nombre de cabeza.
2- Luego el segundo elemento es el cuerpo cola, retorna siempre la
cabeza de la lista.
3- La cabeza de una lista siempre va hacer un elemento y la cola va hacer
una lista en general. Se puede hacer combinadas tanto el head y tail.

Ejemplo:
List2=(5, (3,2(14,9,3), ( ),4),2,(6,3,10) )
1- head (tail (list2))= 3
2- head ( tail (tail (tail (list2))))= 6
3- head (tail (tail ( head (tail (list2))))= (14,9,3).

Otras operaciones de inters en la lista general

201

1- firts (list)
2- info (element)
3- nodetype (elemt)

L1
L2

null

null

METODO DEL APUNTADOR

L1

null

METODO DEL COPIADO


L2

El mtodo del apuntador y el copiado


El mtodo del apuntador lo que hace es que un puntero externo apunte a la
cola de la lista.
El copiado es copiar elemento a elemento a la lista L1.
VENTAJAS Y DESVENTAJAS DE LOS 2 METODOS
El apuntador si la lista L2 sufre un cambio tambin lo sufre L1.
El copiado al afectar la lista L2 no se afecta la lista L1 ya que es una replica o
copia.
En los sistemas operativos se utiliza mas el mtodo del apuntador, porque el
copiado ocupa el doble de memoria del apuntador

202

Ejemplo 1:
1) L = Crlist (2, Crlist (9, 7), 6, 4);
2) L1 = tail (tail (L));
3)Sethead (L1, L);
4) L2 = head (tail (L));
5) L2 = tail (L2);
6) Sethead (L2, L);

1) Y 2)

L1

6
9

4
7

null

null

3)
L1
2

null

null

L2
9

null

4)
L1
2
L
9

L2

203

null

5)
L1
2

null

L
9

null

L2

6)
L1
2

null

L
9

null
L2

Ejemplo 2:
List1 = (4, (2, (a, b), (3,(z, (4, y, x), m ), k), 20), s, d)

4
null

null
204

null

4
3

List1
20

null

b
Head ( tail( tail ( list1))) = S

null

Ejemplo 3:
(a, 5, y, (b, 6), 23, w, (23,g), t)
head (tail (tail (list))) = y
tail (tail (tail ( list))) = (b, 6), 23, w, (23, g), t

LISTAS Y LENGUAJES DE PROGRAMACIN


Al aplicar funciones como head y tail a x en forma directa. En lugar de ello el
programador tiene que implementar listas escribiendo los procedimientos y
funciones necesario para su manipulacin. Otros lenguajes, sin embargo,
contienen listas como estructura de datos elementales como las operaciones
crlist, head, tail , addon, sethead y settail construidas dentro de lenguajes(un
ejemplo de un lenguaje de este tipo es LIST).

205

null

Como los objetos fundamentales son, listas y objetos de datos en lugar


de nodo, el programador no puede ser responsable de la asignacin y
liberacin de nodos individuales. En lugar de ello, cuando un programa
emite una instruccin como
Listas: Son series de elementos en los que cada uno est relacionado con su
antecesor y predecesor. En el caso de que el elemento primero no tenga
anteceder ni el ltimo, predecesor, entonces se denominan lineales, si por el
contrario estos se relacionan entre s, son circulares. Cualquiera de ellas
permite operaciones de insercin, eliminacin, etc. y son la base de las pilas y
las colas.

LENGUAJES DE PROGRAMACIN.
En informtica, cualquier lenguaje artificial que puede utilizarse para definir una
secuencia de instrucciones para su procesamiento por un ordenador o
computadora. Es complicado definir qu es y qu no es un lenguaje de
programacin. Se asume generalmente que la traduccin de las instrucciones a
un cdigo que comprende la computadora debe ser completamente
sistemtica. Normalmente es la computadora la que realiza la traduccin.
Lenguajes de bajo nivel:
Vistos a muy bajo nivel, los microprocesadores procesan exclusivamente
seales electrnicas binarias. Dar una instruccin a un microprocesador
supone en realidad enviar series de unos y ceros espaciadas en el tiempo de
una forma determinada. Esta secuencia de seales se denomina cdigo
mquina. El cdigo representa normalmente datos y nmeros e instrucciones
para manipularlos. Un modo ms fcil de comprender el cdigo mquina es
dando a cada instruccin un mnemnico, como por ejemplo STORE, ADD o
JUMP. Esta abstraccin da como resultado el ensamblador, un lenguaje de muy
bajo nivel que es especfico de cada microprocesador.
Los lenguajes de bajo nivel permiten crear programas muy rpidos, pero que
son a menudo difciles de aprender. Ms importante es el hecho de que los
programas escritos en un bajo nivel sean altamente especficos de cada

206

procesador. Si se lleva el programa a otra mquina se debe reescribir el


programa desde el principio.
Lenguajes de alto nivel
Por lo general se piensa que los ordenadores son mquinas que realizan
tareas de clculos o procesamiento de textos. La descripcin anterior es slo
una forma muy esquemtica de ver una computadora. Hay un alto nivel de
abstraccin entre lo que se pide a la computadora y lo que realmente
comprende. Existe tambin una relacin compleja entre los lenguajes de alto
nivel y el cdigo mquina.
Los lenguajes de alto nivel son normalmente fciles de aprender porque estn
formados por elementos de lenguajes naturales, como el ingls. En BASIC, el
lenguaje de alto nivel ms conocido, los comandos como "IF CONTADOR = 10
THEN STOP" pueden utilizarse para pedir a la computadora que pare si
CONTADOR es igual a 10. Por desgracia para muchas personas esta forma de
trabajar es un poco frustrante, dado que a pesar de que las computadoras
parecen comprender un lenguaje natural, lo hacen en realidad de una forma
rgida y sistemtica.
Lenguaje ensablador (le).
Este intenta de flexibilizar la representacin de los diferentes campos. Esa
flexibilidad se consigue no escribiendo los campos en binario y aproximando la
escritura al lenguaje.

Intrpretes y compiladores
La traduccin de una serie de instrucciones en lenguaje ensamblador (el cdigo
fuente) a un cdigo mquina (o cdigo objeto) no es un proceso muy
complicado y se realiza normalmente por un programa especial llamado
compilador. La traduccin de un cdigo fuente de alto nivel a un cdigo
mquina tambin se realiza con un compilador, en este caso ms complejo, o

207

mediante un intrprete. Un compilador crea una lista de instrucciones de cdigo


mquina, el cdigo objeto, basndose en un cdigo fuente. El cdigo objeto
resultante es un programa rpido y listo para funcionar, pero que puede hacer
que falle el ordenador si no est bien diseado. Los intrpretes, por otro lado,
son ms lentos que los compiladores ya que no producen un cdigo objeto,
sino que recorren el cdigo fuente una lnea cada vez. Cada lnea se traduce a
cdigo mquina y se ejecuta. Cuando la lnea se lee por segunda vez, como en
el caso de los programas en que se reutilizan partes del cdigo, debe
compilarse de nuevo. Aunque este proceso es ms lento, es menos susceptible
de provocar fallos en la computadora.
Logo:

En informtica, lenguaje

de

programacin de

ordenadores o

computadoras, desarrollado en 1968 por Seymour Papert en el MIT, que se usa


frecuentemente en la enseanza de lenguaje de programacin a nios. Una
caracterstica importante de Logo son los grficos de tortuga, que permiten al
programador hacer dibujos simples dirigiendo los movimientos de la tortuga en
la pantalla hacia adelante, hacia la derecha o la izquierda. Una vez que
dominan el entorno sencillo del dibujo, el programador (normalmente un nio o
una nia) empieza a descubrir las caractersticas ms sofisticadas del lenguaje,
que estn basadas fundamentalmente en el lenguaje de programacin LISP.
Logo est considerado como un lenguaje para la formacin, a pesar de que
algunas empresas intentaron que tuviera una mayor aceptacin en los crculos
profesionales de programacin.
Macro: En aplicaciones de ordenador o computadora, un conjunto de
pulsaciones de teclas, acciones o instrucciones grabadas y ejecutadas
mediante una simple pulsacin de tecla o una instruccin.

As se evita la introduccin repetitiva de instrucciones, se minimizan los errores


tipogrficos y se permite a los usuarios que no conozcan el programa
reproducir conjuntos de instrucciones previamente grabados por alguien ms
experto en la aplicacin. Si la aplicacin incluye tambin un lenguaje de macros
que responda a variables e instrucciones condicionales, el usuario puede
tambin controlar el resultado de un procedimiento, haciendo que la macro
responda de forma diferente bajo condiciones diversas. En un lenguaje de
208

programacin, tal como el lenguaje C o ensamblador, una macro es un nombre


que define un conjunto de instrucciones que sern sustituidas por la macro
cuando el nombre de sta aparezca en un programa (proceso denominado
expansin de macros) en el momento de compilar o ensamblar el programa.
Las instrucciones de macros se pueden guardar en el programa mismo o en un
archivo separado que el programa pueda identificar.
Que es LISP
LISP, en informtica, acrnimo de List Processing. Un lenguaje de
programacin para ordenadores o computadoras orientadas a la generacin de
listas, desarrollado en 1959-1960 por John McCarthy y usado principalmente
para manipular listas de datos. El lenguaje LISP constituy un cambio radical
con respecto a los lenguajes procedurales (FORTRAN, ALGOL) que se
desarrollaban por entonces. El LISP es un lenguaje interpretado, en el que
cada expresin es una lista de llamadas a funciones. Este lenguaje se sigue
utilizando con frecuencia en investigacin y en crculos acadmicos, y fue
considerado durante mucho tiempo el lenguaje modelo para la investigacin de
la inteligencia artificial (IA), aunque el Prolog ha ganado terreno durante los
ltimos aos.
Lisp

es

un

lenguaje

muy

utilizado

en

Inteligencia

Artificial.

EL LISP es, como se sabe, uno de los lenguajes de programacin en uso ms


antiguos. A finales de los aos 50,John MvCarthy, a quien se considera como
el padre de la inteligencia

artificial, dise este lenguaje para servir como

herramienta de programacin en esta disciplina.


La idea

de LISP surgi a partir de un sistema lgico llamado lambda

calculusdesarrollado por

Alonso Church. Existen diversas variantes (o

dialectos) de LISP, entre las cuales Scheme T, etc.


LISP lleg a ser fundamentalmente como lenguaje de programacin para las
investigaciones de inteligencia artificial, y sigue an

hoy siendo uno de los

mas utilizados en este campo. En la dcada de los 80 se intent estandardizar


el lenguaje.
Como resultado surgi el Common LISP
Una gran ventaja de usar LISP se suele citarse frente a las dems alternativas
de los lenguajes usados para la inteligencia artificiales su gran flexibilidad.
209

El hecho de que tanto los programas como los datos compartan una misma
estructura, las listas, hace que los propios programas se puedan modificar a si
mismo.
En LISP se pueden crear con gran facilidad lenguajes o estructura de dato
adaptados al tipo de problema del que se trate.
Las armas fundamentales para esta flexibilidad son los macro y la capacidad
para crear funciones en tiempo de ejecucin.
El LIPS proporciona un entorno de muy alto nivel (en trminos de
programacin), ya que le Common LISP proporciona un conjunto de mas de
700 funciones primitivas, lo que permite despreocuparse

de numerosos

detalles de implementacin de bajo nivel a diferencia de lo que ocurre con


lenguajes como el C.

EJERCICIOS PROPUESTOS
I- IMPLEMENTE LAS RUTINAS HEAD Y TAIL
1- list= (b, 8, z, (c, 6), 58, (h, 10 ) , 8 )
head (tail (tail (list)))
tail (tail (tail ( list)))
2 list = (s, 2, 3, (l, 2), 7)
Tail (head(tail(list)))
Head(head(tail(list))
II- GRAFIQUE
1- List(4,2,5,(p,2),( ))
2- List(2, 1,(j,5),(b, k),4)

1.2.1.1.- COMPACTACIN DE MEMORIA


Cuando un proceso llega y necesita memoria, el sistema operativo busca en la
tabla de huecos alguno lo suficientemente grande para el proceso. Si el hueco
es muy grande, lo parte en dos. Una parte es asignada al proceso y la otra se
identifica como hueco. Cuando el proceso termina y la memoria es liberada, el
espacio es identificado como un hueco ms en la tabla y si el nuevo hueco es

210

adyacente con otro, ambos huecos se unen formando un solo hueco ms


grande. En ese momento se debe de checar si no existen procesos a los que
este nuevo hueco pueda darles cabida.

Fig. 5. Ejemplo de compactacin de huecos no adyacentes.


En la figura 5 se muestra como se modifica el mapa de la memoria despus de
compactar huecos no adyacentes generados despus de intercambios
realizados en el ejemplo de la figura 4.

1.2.1.2.- ASIGNACIN DINMICA


El proceso de compactacin del punto anterior es una instancia particular del
problema de asignacin de memoria dinmica, el cual es el cmo satisfacer
una necesidad de tamao n con una lista de huecos libres. Existen muchas
soluciones para el problema. El conjunto de huecos es analizado para
determinar cul hueco es el ms indicado para asignarse. Las estrategias ms
comunes para asignar algn hueco de la tabla son:
Se ha demostrado mediante simulacros que tanto el primer y el mejor ajuste
son mejores que el peor ajuste en cuanto a minimizar tanto el tiempo del
almacenamiento. Ni el primer o el mejor ajuste es claramente el mejor en
trminos de uso de espacio, pero por lo general el primer ajuste es ms rpido.
1.2.5.- FRAGMENTACIN
La fragmentacin es la memoria que queda desperdiciada al usar los mtodos
de gestin de memoria que se vieron en los mtodos anteriores. Tanto el primer
ajuste, como el mejor y el peor producen fragmentacin externa.
La fragmentacin es generada cuando durante el reemplazo de procesos
quedan huecos entre dos o ms procesos de manera no contigua y cada hueco
no es capaz de soportar ningn proceso de la lista de espera. Tal vez en
conjunto si sea espacio suficiente, pero se requerira de un proceso de de
fragmentacin de memoria o compactacin para lograrlo. Esta fragmentacin
se denomina fragmentacin externa.

211

Existe otro tipo de fragmentacin conocida como fragmentacin interna, la cual


es generada cuando se reserva ms memoria de la que el proceso va
realmente a usar. Sin embargo a diferencia de la externa, estos huecos no se
pueden compactar para ser utilizados. Se debe de esperar a la finalizacin del
proceso para que se libere el bloque completo de la memoria.

RECOLECCION DE BASURA
Como hemos visto con anterioridad, existen lenguajes que ofrecen liberacin
implcita de memoria. Aquellos bloques de memoria que no sean accesibles
desde ninguna referencia (puntero) del programa reciben el
nombre de basura (garbage)
La memoria basura deber ser liberada por un lenguaje con liberacin implcita
de memoria heap. Este proceso de liberacin implcita de memoria recibe el
nombre de recoleccin de basura (garbage collection)
La recoleccin de basura es un proceso dinmico.
En el caso de un compilador, deber generar cdigo encargado de realizar esta
operacin.
Ejemplo
El siguiente programa Java posee bloques inaccesibles (basura) en el punto
de ejecucin indicado.
class Lista {
int valor;
Lista sig;
public Lista(int v,
Lista s) {
valor=v;
sig=s;
}
}
class Arbol {
int valor;
Arbol izq,der;

212

public Arbol(int v,
Arbol i,Arbol d) {
valor=v;
izq=i;
der=d;
}
}
public class Basura {
static Lista crearLista() {
Lista l=new Lista(1,null),
l2=new Lista(2,l);
l.sig=l2;
return l;
}s
tatic Arbol crearArbol() {
return new Arbol(3,
new Arbol(4,null,null),
new Arbol(5,null,null));
}p
ublic static void main(
String[] args) {
crearLista();
Arbol a=crearArbol();
}
}
12345
a
Variables
Heap
Punto de Ejecucin Basura
Veremos las siguientes tcnicas de recoleccin de
basura.
1-Marcar y borrar (mark and sweep)
213

2- Contadores de referencias
3- Recoleccin con copia (copying collection)
4- Recoleccin generacional
5- Recoleccin incremental y concurrente
Se ejecutan cuando se solicita un bloque de memoria y se detecta que sta
es escasa.
El grado de escasez es parametrizable en funcin del algoritmo
Si despus de llamar a la recoleccin de basura no se obtiene beneficio, se
debe pedir ms memoria al sistema operativo
Marcar y Borrar
Los bloques heap en memoria forman un grafo dirigido
Las variables locales y globales de un programa de tipo referencia (puntero)
constituyen la raz de dicho grafo.
El algoritmo de marcar y borrar realiza un recorrido en profundidad del grafo
de bloques. Parte de las races (variables)
Todo nodo del grafo que haya visitado lo marca como no basura
Una vez finalizado el recorrido del grafo, todos aquellos nodos que no hayan
sido marcados son basura
stos son incluidos en la lista de bloques libres

Ejemplo.
12345
a
Variables
Heap
Bloques Libres
214

Caractersticas de Algoritmo
1- El algoritmo de marcar y borrar posee una complejidad proporcional al
nmero total de bloques de la memoria heap
2- El nico modo que tiene para controlar la fragmentacin externa es mediante
la fusin de bloques.
3- No requiere elevadas cantidades de memoria heap para ser implementada.
Implementaciones
Contador de Referencias
La primera parte del algoritmo marcar y borrar detecta si los bloques heap son
accesibles desde las variables referencia. El algoritmo de contador de
referencias se basa mantener esta informacin a lo largo de la ejecucin del
programa. Todo bloque es aumentado con un campo contador de referencias
Cada vez que a una referencia se le asigne el valor de un
bloque, su contador ser incrementado.

Cada vez que se deje de hacer

referencia a un bloque (se elimina la referencia o pasa a apuntar a otro


bloque)su contador ser decrementado.
Si el contador de referencias de un bloque alcanza el valor 0 este es aadido
a la lista de bloques libres.

Se decrementan todas los contadores de las

referencias que existan en dicho bloque

Contador de referencias
Este algoritmos es sencillo de implementar pero posee dos graves
inconvenientes.
1. No elimina los bloques basura que tengan referencias mutuas directas o
indirectas (cclicas).
215

2. El incremento y decremento de los contadores de referencia es muy


costoso (bajo rendimiento).Debe generarse cdigo extra cada vez que se
modifica el valor de una referencia 1 2 3 4 5 a Variables Heap 1111
Hay dos posibles soluciones al problema de las referencias cclicas
Combinar esta tcnica ocasionalmente con otro algoritmo de recoleccin como
marcar y borrar. Especificar esta caracterstica en el manual de referencia del
lenguaje y hacer responsable al
programador de evitar estos escenarios. En este caso, no estaramos en un
punto intermedio entre gestin implcita y explcita.
En general, los dos inconvenientes de esta tcnica hacen que apenas se
emplee.
Existen implementaciones para los lenguajes Smalltalk, Modula-2, awk y Perl
Recoleccion con copia
El principal problema del algoritmo de marcar y borrar es la
fragmentacin externa que genera los bloques libres estn entremezclados
con los bloques asignados.
El algoritmo de recoleccin con copia (copying collection) elimina totalmente
la fragmentacin externa
Divide la memoria heap en dos secciones de igual tamao. La zona origen
(from) posee todos los bloques asignados (tambin puede tener libres).

La

zona destino (to) nicamente constituye una zona


de memoria libre compacta.
La operacin de asignacin de memoria se lleva a cabo en la zona origen,
mediante alguno de los algoritmos vistos en el punto anterior (asignacin de
memoria heap).

Recoleccin con Copia


Cuando se solicite un bloque de memoria y no haya memoria disponible en la
zona origen, se ejecutar el recolector de basura

216

Se recorre el grafo partiendo de sus races. Cada vez que se visita un nodo
(accesible) se mueve a la zona destino. Las referencias (puntero) al bloque
medido son actualizadas a la nueva direccin.
Una vez finalizado el proceso el bloque origen posee toda su memoria libre
(basura) el bloque destino
a) tiene toda la memoria asignada,
b) no posee basura
c) no est fragmentada
En este momento el bloque destino para a ser origen y el origen destino
(cambio de roles).
Ejemplo Recoleccin con Copia
1234
5
a
Variables
Origen Destino
345
a
Variables Destino Origen
Recoleccin con copia
Caractersticas del Algoritmo
El algoritmo de recoleccin con copia posee una complejidad proporcional al
nmero de bloques accesibles (marcar y borrar era proporcional al nmero total
de bloques).

Evita la fragmentacin, puesto que en todo proceso de

recoleccin se lleva a cabo un proceso de compactacin.


R e una mayor cantidad de memoria heap que el marcar y borrar
En la actualidad, la mayor parte de sus implementaciones utiliza generaciones
(prximo punto).
Existen implementaciones antiguas de Lisp, Scheme yML
Recoleccin Generacional

217

Esta tcnica se apoya sobre una serie de criterios empricos detectados en la


mayora de los lenguajes de programacin [Lieberman]
En la mayora de programas se produce que

Los bloques con mayor

probabilidad de constituir basura son los recin creados (jvenes)


Aquellos bloques que han superado un mayor nmero de recogidas de basura
tienen una elevada probabilidad de seguir siendo accesibles esto implica que el
proceso de recoleccin de basura debera aplicarse en mayor medida a los
bloques ms jvenes

Jerarquizando los objetos en funcin del tiempo

transcurrido tras su creacin se obtendr un mayor


rendimiento (no es necesario procesar todos los bloques en todas las
recolecciones) .equier

Recoleccin Generacional
En esta tcnica la memoria heap es dividida en generaciones (Gi)
En Go estn los bloques ms jvenes. Todo bloque que Gj posee es mayor
a los bloques Gj-1.
Un criterio emprico comnmente empleado es utilizar tamaos de
generaciones exponencialmente mayores que el tamao de la generacin
anterior.
Cuando se solicita la asignacin de memoria y no
existen bloques libres en G0,

Se lleva a cabo la recoleccin de basura

comenzado nicamente por aquellas variables raz que apunten


a bloques en G0.
El algoritmo de recoleccin slo realiza en G0 Sobre este bloque se aplica uno
de los algoritmos de recoleccin vistos previamente

Caso Especial
Existe un caso especial a tener en cuenta que provoca que bloques accesibles
sean identificados como basura.
Este caso se da cuando hay una referencia de otra generacin apuntando a un
bloque de G0.

218

Por el modo en le que se construyen los objetos y variables en los lenguajes


de programacin, se ha demostrado empricamente que este caso se produce
en escasas ocasiones [Lieberman]
La solucin a este problema pasa por almacenar este tipo
de referencias y aadirlas a las variables raz antes del proceso de
recoleccin .
G0
G1
G2
variables
G0
G1
G2
variables
Tras una serie de recolecciones de G0, en G1 se podr haber
generado una serie de bloques basura. Puesto que existir un conjunto de
enlaces entre los elementos del mdulo G0 y los de G1, ser menos costoso
realizar la recoleccin para ambas generaciones simultneamente.
Toda recoleccin en Gi se realiza junto a todas sus generaciones anteriores.
Un bloque promociona de Gi a Gi+1 tras sobrevivir a 2 o 3
recolecciones de Gi. Esta promocin puede realizarse de un modo sencillo
empleando recoleccin con copia (evitando adems la
fragmentacin).
Los estudios empricos con lenguajes funcionales y OO con recoleccin de
basura han concluido. Se elimina en torno al 90% de los bloques de G0 cada
vez que se pasa el recolector.

Llevar a cabo este proceso supone una

complejidad logartmica respecto al nmero de bloques (las generaciones


crecen exponencialmente).

219