You are on page 1of 15

Procesamiento de Datos I Lic: Juan Espinoza

ARCHIVOS EN C++

Práctica 6:

 Dispersión o Hashing: Definiciones.


 Reducción de colisiones.
 Algoritmos de dispersión.
 Implementación de funciones básicas.
 Resolución de colisiones.

 Dispersión o Hashing:

Existen 3 modos principales de acceder a los archivos: en forma


secuencial, a través de un índice y de manera directa. La dispersión
representa la principal forma de organización de archivos para
permitir el acceso directo.

La dispersión o hashing es una técnica para generar una dirección


base única para un registro dado. El hashing se usa cuando se
requiere acceso rápido a una clave (o a su registro correspondiente).

Una función de dispersión h(k), transforma una llave k en una


dirección. La dirección resultante se usa como base para la búsqueda
y de almacenamiento de registros.

A las claves que por hashing se convierten en la misma dirección


se denominan sinónimos. Este caso se conoce como colisión y existen
medios para resolverlo.

 Reducción de colisiones:

1) Esparcir los registros: Consiste en encontrar un algoritmo


de dispersión que distribuya los registros en forma más aleatoria
en el archivo.

2) Usar memoria adicional: Emplear más espacios para almacenar


con el fin de evitar colisiones.

3) Colocar más de un registro en una sola dirección: Al momento


de crear los registros físicos, se puede reservar espacio para
varios sinónimos. Estas direcciones se llaman compartimientos.

 Algoritmos de dispersión:

Todo algoritmo de dispersión debe seguir los siguientes pasos


generales:

1
Procesamiento de Datos I Lic: Juan Espinoza

1) Representar la clave en forma numérica.

Si la clave ya es un número, entonces este paso ya está hecho. Si


es una cadena de caracteres, se usa el código ASCII de cada carácter
para formar el número.

Ejm: Para un campo de 10 bytes con el registro Maria...

M a r i A < - - espacios - - >


77 97 114 105 97 32 32 32 32 32

2) Desglosar y sumar.

Significa tomar pedazos del número y sumarlos.

Ejm:

7797 | 114105 | 9732 | 3232 | 3232

7797 + 114105 + 9732 + 3232 + 3232= 138098

3) Dividir entre el tamaño del espacio de direcciones.

Consiste en recortar el número producido en el paso 2, para que


este dentro del intervalo de direcciones de registros en el archivo.

Se puede hacer dividiendo el número obtenido entre el tamaño de


direcciones del archivo; el residuo será la base del registro. El
residuo producido será un número entre 0 y n-1.

El número de direcciones asignadas para el archivo puede variar al


ser elegido. Se elige un número tan próximo como sea posible al
tamaño deseado del espacio de direcciones. Es común usar un número
primo como divisor porque los números primos tienden a distribuir
residuos en forma mucho más uniforme que los números compuestos.

Algunos algoritmos de dispersión:

 Dispersión por resto: Se basa en la ecuación h(k)= k mod M, donde:


h(k), es la función de dispersión que devolverá el valor de la
dirección a utilizar.
M, es el número de registros físicos disponibles para
almacenar.
K, es el campo clave del registro a dispersar.

El algoritmo de este método sería:

int dispersion1 (int k, int M)


{
int h;
h=k%M;
return h;}

2
Procesamiento de Datos I Lic: Juan Espinoza

En el caso de la clave sea una cadena, el algoritmo es:

int dispersion1 (char k[10], int M)


{
int h, t, suma=0;
t=strlen(k); //Devuelve el número de caracteres de la cadena
for (int i=0; i<t; i++)
suma=suma+k[i];
h=suma%M;

return h;
}

 Dispersión por doblez: Se basa en tomar el campo de dispersión y


dividirlo en 2 partes, para luego realizar operaciones aritméticas
con las subdivisiones del número. En C++ la función por doblez sería:

int dispersion(char k[10], int M)


{
int h, t, d, x, y, z, i, j=0;
char b[5], c[5];

t=strlen(k); //devuelve el número de caracteres de la cadena


d=t/2;

for (i=0; i<d; i++)


b[i]=k[i];

x=atoi(b); //convierte la cadena en entero.

for (i=d; i<t; i++)


{
c[j]=k[i];
j++;
}

y=atoi(c); //convierte la cadena en entero.


z=x+y;
h=z%M;

return h;
}
 Dispersión por elección de dígitos en campo clave: Si el archivo
tiene M registros físicos, entonces tomaremos los M-1 dígitos
intercalados del inverso del campo de dispersión. Es decir, tomaremos
las posiciones 0, 2, 4, ... digitos de derecha a izquierda del campo
clave. En C++ la función sería:

3
Procesamiento de Datos I Lic: Juan Espinoza

int dispersion (char k[10], int M)


{
int h, t, i, j=0, aux, x;
char k2[10], c[10], d[5];

aux=M-1;
itoa(aux,c,10); //Convierte un entero en una cadena
t=strlen(c); //Devuelve el número de caracteres
strcpy(k2,k1);
strrev(k2); //Invierte la cadena de caracteres
for (i=0; i<t; i++)
{
d[i]=k2[j];
j=j+2;
}
x=atoi(d); //Convierte la cadena de caracteres en entero
h=x%M;

return h;
}

 Dispersión por extracción: Consiste en tomar los dígitos 0, 2,


4, ... (de derecha a izquierda) y los dígitos 0, 2, 4, ... (de
izquierda a derecha) del campo clave, para luego realizar operaciones
aritméticas. En C++ la función sería:

Int dispersion(char k[10], int M)


{
int h, t=0, y, i, j=0, x;
float c1;
char k2[10], c[5], d[5];

strcpy(k2,k);
strrev(k2); //Función para invertir una cadena
c1=strlen(k)/2.0;
c1=ceil(c1);//Redondea c1 al entero mayor o igual más próximo

for (i=0; i<c1; i++)


{
c[j]=k[t];
d[j]=k2[t];
j++;
t=t+2;
}

x=atoi(c ); //Convierte la cadena de caracteres en entero


y=atoi(d); //Convierte la cadena de caracteres en entero

h=(x+y)%M;

return h;
}

4
Procesamiento de Datos I Lic: Juan Espinoza

 Dispersión por elevación al cuadrado: Se fundamenta en tomar los


últimos 6 dígitos de k y elevarlos al cuadrado. Luego se toman los
primeros 6 dígitos resultantes y se dividen en 2 partes para realizar
operaciones aritméticas. En C++ la función sería:

int dispersion (char k1[10], int M)


{
int h, y, I, c1, j=0, k=0, x;
double t,z;
char k2[10], c[30], d[10], a[5], b[5];

c1=strlen(k1); //Devuelve el número de caracteres

for (i=c1-6; i<c1; i++)


{
k2[j]=k1[i];
j++;
}

z=atof(k2); //Convierte una cadena en un dato double


t=z*z;
gcvt(t,30,c); //Convierte un dato double en una cadena

for (i=0; i<6; i++)


d[i]=c[i];

for (i=0; i<3; i++)


a[i]=d[i];

for (i=3; i<6; i++)


{
b[k]=d[i];
k++;
}

x=atoi(a); //Convierte la cadena de caracteres en entero


y=atoi(b); //Convierte la cadena de caracteres en entero
h=(x+y)%M;

return h;
}

5
Procesamiento de Datos I Lic: Juan Espinoza

 Implementación de funciones básicas: Entre las funciones más


empleadas se encuentran:

1) Declaración del archivo: Cuando el archivo se declara, se necesita


de la siguiente información:

_ Nombre del archivo y modos de apertura.


_ Formato de los registros físicos.
_ Organización relativa de los registros físicos.
_ Técnicas de hashing a emplear.
_ Técnicas para resolver las colisiones.

2) Creación del archivo: Se recomienda asignarle espacio físico al


archivo antes de comenzar a almacenar registros en el. Para esto se
crea un archivo con espacios vacíos para todos los registros. Se
inicializan los M registros con una estructura vacía empleando el
método:
descriptor.write((char*)&struct, sizeof(struct))

Donde la estructura posee comillas vacias (“ ”) para campos de tipo


char, 0 para campos enteros y 0.0 para campos de tipo real.

Un método recomendable consiste en agregarle como primer campo a


la estructura un campo marcador de tipo entero, donde:

Struct.marca=0, si el registro está vacío.


Struct.marca=1, si el registro está ocupado.

En C++ la función crear sería de la forma como aparece en el


programa anexo al final.

3) Carga de un archivo: Para cargar un archivo, el programa usa la


función h(k) para producir una dirección base para cada campo clave
k. Luego busca espacio libre para el registro comenzando con la
dirección base, si esta ocupada, se procede a buscar otra posición
dependiendo de la función para resolver colisiones diseñada.

4) Actualización de un archivo con dispersión: Un archivo se


actualiza de modo directo. El programa usa un algoritmo de búsqueda o
dispersión para localizar el registro solicitado, hace las
modificaciones especificadas y después reescribe el registro en el
punto donde lo encontró.

5) Eliminación en un archivo con dispersión: Al eliminar registros se


debe acceder de forma directa a el, empleando una función de
dispersión. Se debe reutilizar el espacio liberado para las adiciones
posteriores.

6) Otras operaciones:
_ Al leer, verificar la inexistencia de registros en la dirección.
_ La dirección está fuera del límite establecido.

6
Procesamiento de Datos I Lic: Juan Espinoza

_ Un registro ya existe en la dirección (colisión).

7
Procesamiento de Datos I Lic: Juan Espinoza

Un ejemplo de las operaciones básicas con dispersión sería:

#include <fstream.h>
#include <conio.h>
#include <iomanip.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct tipo
{
int marca;
long codigo;
char nombre[20];
};

int dispersion(long k, int M);


void crear(fstream &f, int M);
void cargar(fstream &f, int M);
void mostrar(struct tipo b);
void actualizar(fstream &f, int M);
void eliminar(fstream &f, int M);
void listar(fstream &f);

int main()
{
int M=131, opc;

fstream f;
f.open("nomarch.dat", ios::in| ios::out| ios::binary);

if(f.good()!=1)
{
cerr<<"Error al abrir el archivo";
getch();
return 1;
}

do
{
clrscr();
cout<<"\n[1] Crear";
cout<<"\n[2] Cargar";
cout<<"\n[3] Actualizar";
cout<<"\n[4] Eliminar";
cout<<"\n[5] Listar";
cout<<"\n[6] Salir\n";
cin>>opc;

switch(opc)
{

8
Procesamiento de Datos I Lic: Juan Espinoza

case 1:
crear(f,M);
clrscr();
cout<<"El archivo se inicializo correctamente";
getch();
break;

case 2:
cargar(f,M);
break;

case 3:
actualizar(f,M);
break;

case 4:
eliminar(f,M);
break;

case 5:
listar(f);
break;

case 6:
break;

default:
clrscr();
cout<<"Opcion no valida";
break;

}
}while(opc!=6);

getch();
return 0;
}

int dispersion(long k, int M)


{
int h;

h=k%M;

return h;
}

void crear(fstream &f, int M)


{
tipo a={0,0,""};

for (int i=0; i<M; i++)

9
Procesamiento de Datos I Lic: Juan Espinoza

f.write((char *)&a, sizeof(tipo));


}

void cargar(fstream &f, int M)


{
int h;
long k;
tipo b;

clrscr();
cout<<"\nIntroduzca el codigo: ";
cin>>k;

h=dispersion(k,M);

f.seekg((h)*sizeof(tipo), ios::beg);
f.read((char *)&b, sizeof(tipo));

if(b.marca==0)
{
b.marca=1;
b.codigo=k;
cout<<"\nIntroduzca el nombre: ";
gets(b.nombre);
f.seekp((h)*sizeof(tipo), ios::beg);
f.write((char *)&b, sizeof(tipo));
}
else
{
clrscr();
cout<<"La direccion "<<h<<" esta ocupada";
getch();
}
}

void actualizar(fstream &f, int M)


{
int h, opc;
long k;
tipo b;

clrscr();
cout<<"\nIntroduzca el codigo: ";
cin>>k;

h=dispersion(k,M);

f.seekg((h)*sizeof(tipo), ios::beg);
f.read((char *)&b, sizeof(tipo));

if(b.marca!=0)
{

10
Procesamiento de Datos I Lic: Juan Espinoza

do
{
clrscr();
mostrar(b);
cout<<"\n\nOperaciones disponibles:";
cout<<"\n[1] Modificar nombre";
cout<<"\n[2] Volver al menu principal";
cin>>opc;

switch(opc)
{
case 1:
cout<<"\nIntroduzca el nuevo nombre: ";
gets(b.nombre);
f.seekp((h)*sizeof(tipo), ios::beg);
f.write((char *)&b, sizeof(tipo));
break;

case 2:
break;

default:
break;

}
}while(opc!=2);
}
else
{
clrscr();
cout<<"La direccion "<<h<<" esta vacia";
getch();
}
}

void mostrar(struct tipo b)


{
cout<<"\nDatos del registro: ";
cout<<"\nCodigo: "<<b.codigo;
cout<<"\nNombre: "<<b.nombre;
}

void eliminar(fstream &f, int M)


{
int h;
long k;
tipo b, a={0,0,""};

clrscr();
cout<<"\nIntroduzca el codigo: ";
cin>>k;

11
Procesamiento de Datos I Lic: Juan Espinoza

h=dispersion(k,M);

f.seekg((h)*sizeof(tipo), ios::beg);
f.read((char *)&b, sizeof(tipo));

if(b.marca!=0)
{
clrscr();
f.seekp((h)*sizeof(tipo), ios::beg);
f.write((char *)&a, sizeof(tipo));
cout<<"El codigo "<<k<<" fue eliminado";
getch();
}
else
{
clrscr();
cout<<"La direccion "<<h<<" esta vacia";
getch();
}
}

void listar(fstream &f)


{
tipo b;

clrscr();

cout<<setiosflags(ios::left)<<setw(15)<<"Codigo"<<setw(20)<<"Nombre"<
<endl;

f.seekg(0, ios::beg);
f.read((char *)&b, sizeof(tipo));

while (f.eof()!=1)
{
if(b.marca==1)
{
cout<<setiosflags(ios::left)<<setw(15)<<
b.codigo<<setw(20)<<b.nombre<<endl;
}
f.read((char *)&b, sizeof(tipo));
}

f.clear();
getch();

12
Procesamiento de Datos I Lic: Juan Espinoza

 Resolución de colisiones: Aún cuando un algoritmo de dispersión


sea muy bueno, es probable que ocurran colisiones. Por lo tanto,
cualquier programa de dispersión debe incorporar algún método para
tratar las colisiones. Entre algunas de ellas están:

1) Saturación progresiva: Es un proceso de búsqueda secuencial desde


la dirección de orígen para encontrar la siguiente localidad vacía.
Si la saturación progresiva se usa para almacenar registros, también
debe usarse para recuperarlos.

El método para almacenar registros es:

El método para recuperar registros es:

13
Procesamiento de Datos I Lic: Juan Espinoza

2) Saturación progresiva encadenada: Esta técnica es una extensión de


la saturación progresiva. Consiste en agregar un campo de liga a cada
registro, el cual contiene un número que indica el lugar del
siguiente registro con la misma dirección base.

Ejemplo:

Dirección Llave Liga


0 123 2
1 456 -1
2 789 3
3 147 -1

3) Área de desbordamiento separada: Consiste en almacenar los


registros sinónimos en un área separada del archivo secuencialmente.
Al conjunto de direcciones base se le llama área principal de datos y
al conjunto de sinónimos se le llama área de desbordamiento.

El archivo de desbordamiento puede ser un archivo sencillo


secuencial, y a su vez puede contener un campo de liga para
almacenar la dirección del siguiente sinónimo, siempre y cuando
se trabaje con registros físicos del tamaño fijo.

Para la recuperación de registros se verifica primero, si la


dirección está vacía, sino se compara la clave con la clave del
registro. En caso de que no coincidan, se busca secuencialmente
en el archivo de desbordamiento. Si no esta en este, el
registro no existe.

Ejemplo:

Área Principal Área de desbordamiento


Dirección Clave Dirección Clave Liga
0 123 0 124 1
1 456 1 134 2
2 789 2 223 -1
3 147 3 121 -1

4) Dispersión doble: Consiste en almacenar los registros sinónimos


a una distancia establecida de sus direcciones bases. Cuando sucede
una colisión, se aplica una segunda función de dispersión a la clave
para producir un número c, el cual se agrega a la dirección base para
producir la nueva dirección. Si ya está ocupada, se le agrega c para
producir la nueva dirección. Este método tiende a esparcir los
registros en el archivo y lograr uniformidad.

Ejemplo:

14
Procesamiento de Datos I Lic: Juan Espinoza

h=dispersion1(k);
c=dispersion2(k);
h=h+c;

si archivo[h] esta ocupado


h=h+c;
sino
Almacenar el registro.

5) Compartimientos: Consiste en inicializar un archivo para que


sea capaz de almacenar n registros en la misma dirección.

Para buscar los registros se lee todo el compartimiento para


luego buscar secuencialmente el registro. Cuando un registro se
almacena o extrae, su dirección base se determina por
dispersión.

Ejemplo:

Dirección Compartimiento 1 Compartimiento 2 Compartimiento 3


0 0 | “ “ 0 | “ “ 0 | “ “
1 123 | Inés 843 | Javier
2 271 | Dalila 359 | Berenice 906 | Dorys

15

You might also like