Está en la página 1de 12

UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna

1
1 Introduccin a las estructuras de datos
1.1 La informacin y su significado

En un sentido general, la informacin es un conjunto organizado de datos, que constituyen un
mensaje sobre un determinado ente o fenmeno. De esta manera, por ejemplo, si organizamos los
datos sobre un pas (nmero de habitantes, densidad de poblacin, nombre del presidente, etc.) y
escribimos el captulo de un libro, podemos decir que ese captulo constituye informacin sobre
ese pas. Cuando tenemos que resolver un determinado problema o tomar alguna decisin,
empleamos la informacin.

Desde un punto de vista estricto la informacin tambin esta compuesta de datos, un dato es la
cantidad mnima de informacin no elaborada, sin sentido por s misma, pero que
convenientemente tratada se puede utilizar en la realizacin de clculos o toma de decisiones. Es
de empleo muy comn en el mbito computacional.

Toda informacin esta constituida de datos, pero no todos los datos producen informacin
especfica e inteligente. Los datos adicionales pueden agregar nuevas dimensiones a la
informacin existente, pero la interpretacin requiere criterio humano.
1.1.1 Definicin de Bit y Byte

Un bit (binary digit) es la unidad bsica de informacin, cuyo valor confirma dos posibilidades. Por
ejemplo: un dispositivo electrnico puede estar en una de dos posiciones pero no en ambas al
mismo tiempo, el hecho es que al estar en la posicin de encendido o apagado, ste representa
un bit de informacin. Si un dispositivo puede estar en ms de dos estados, al tomar un estado en
particular se tiene ms de un bit de informacin.

Los bits estn agrupados en unidades mas grandes de informacin llamados bytes, que son
unidades de almacenamiento compuestos por 8 bits en la memoria de las computadoras. Por lo
general un byte representa un carcter alfanumrico codificado.

La computadora es un sistema rpido y exacto para manipular smbolos (datos), diseado para
aceptar y almacenar datos de entrada, procesarlos y producir resultados. De esta forma la
agrupacin de bits puede representar datos que pueden ser manipulados en una computadora.
1.2 Almacenamiento de informacin

El almacenamiento de informacin en dispositivos electrnicos consiste en identificar, clasificar y
estructurar los datos de informacin en unidades de almacenamiento computacional, para
posteriormente ser accedidas y operadas. Debido a las caractersticas computacionales los bits y
los bytes resultan la manera ms sencilla de almacenamiento de informacin en dispositivos
electrnicos, de sta manera el sistema numrico binario es ampliamente empleado para
representar, inicialmente, nmeros enteros sin signo y posteriormente se amplio el rango a los
nmeros negativos, reales y caracteres.
1.2.1 Representacin interna de los datos

Nmeros binarios sin signo

En el sistema binario cada posicin del bit representa una potencia de 2. El bit mas a la derecha
representa 2
0
el cual es igual a 1, la siguiente posicin a la izquierda representa 2
1
el cual es 2, la
siguiente posicin representa 2
2
es decir 4, y as sucesivamente un numero binario entero
representa la suma de potencias de 2. Los ceros son ausentes en la suma de potencias.
UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
2

De esta forma el numero binario 00100110 representa la suma de potencias (2
5
+ 2
2
+ 2
1
) que es
igual a 38. Bajo esta interpretacin, cualquier cadena de bits de tamao n representa un nmero no
negativo entre 0 y 2
n -1
.

Nmeros binarios con signo

Las computadoras deben interpretar nmeros positivos y negativos. Los nmeros binarios se
caracterizan por su magnitud y su signo. El signo indica si el nmero es positivo o negativo y la
magnitud el valor del nmero. Existen 3 formas de representar nmeros binarios enteros con signo:

a) Signo magnitud.
b) Complemento a 1.
c) Complemento a 2.

a) Signo Magnitud: Los nmeros positivos y negativos tienen la misma notacin para los bits de
magnitud pero se diferencian en el bit del signo. El bit del signo es el bit situado ms a la
izquierda en el nmero binario. En nmeros positivos se emplea el bit "0" y en nmeros
negativos el bit "1". Adems el nmero no debe estar complementado.

De esta manera 21 se expresa en binario de 8 bits 0001 0101, donde el primer bit "0" denota el bit
de una magnitud positiva. Por otro lado, 21 se expresa en binario 1001 0101, donde el primer bit
"1" denota el bit de una magnitud negativa.

El sistema de signo magnitud contiene el mismo numero de enteros positivos y negativos en el
rango de 2
n-1
1 a 2
n-1
1, con dos posibles representaciones del cero.

Ejemplos

01111111
2
= +127

y 11111111
2
= -127
00000000
2
= +0 y 10000000
2
= -0


b) Complemento a 1: Se obtiene cambiando los unos por ceros y los ceros por unos. La
representacin de nmeros positivos en complemento a 1 sigue las mismas reglas del sistema
signo-magnitud y la representacin de los nmeros negativos en complemento 1 es el
complemento a 1 del nmero positivo (operador de negacin).

As el nmero 21 se expresa en complemento a 1 de 8 bits como 0001 0101, donde el primer bit
"0" denota el bit de una magnitud positiva. El nmero 21, se obtiene por medio del complemento
a 1 del nmero positivo 0001 0101 el cual es 1110 1010.

Tome en cuenta que 2
n-1
y 2
n-1
no puede ser representado en la notacin de complemento a 1.

c) Complemento a 2: Las computadoras utilizan la representacin binaria en complemento a 2
para representar nmeros negativos. La representacin de nmeros positivos en complemento
a 2 sigue las mismas reglas del sistema signo-magnitud y la representacin de los nmeros
negativos en complemento a 2 se obtiene de la siguiente forma:

1. Se representa el nmero decimal dado en magnitud positiva.
2. El nmero de magnitud positiva se representa en forma binaria positiva.
3. Se obtiene el complemento 1 del nmero binario mediante el cambio de los unos por
ceros y viceversa.
4. Al complemento 1 se le suma uno y el resultado es la representacin en el
complemento 2.

UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
3
Representar el nmero 5 en binario, utilizando el complemento a 2 con 8 bits:
1. 5 se cambia a 5.
2. Escribimos el nmero 5 en binario de 8 bits 0000 0101
3. Obtenemos el complemento a 1 de 0000 0101 es decir, 1111 1010
4. Al complemento del nmero anterior se la suma 1. El resultado es 1111 1011.
Podemos comprobar la obtencin del negativo del nmero inicial utilizando el mtodo del
complemento a 2:

11111011

= (-128 + 64 + 32 +16 + 8 + 0 + 2 + 1)
10
= - 5

El rango de nmeros que se puede representar es 2
n-1
y 2
n-1
1, es decir un numero de 8 bits (1
byte) puede representar valores entre -128 y 127. Adems existe solo una representacin para el
nmero 0:

0000 0000 signo magnitud
1111 1111 complemento 1
1 0000 0000 complemento 2

Puesto que solo se permiten 8 bits, el bit ms a la izquierda se elimina, quedando nicamente el
0000 0000.

Nmeros reales

El mtodo usual para representar nmeros reales es la notacin de punto flotante. Existen muchas
variantes de la notacin de punto flotante con caractersticas propias. El aspecto clave es que los
nmeros reales estn representados por un numero llamado mantisa que se multiplica por una
base que es una potencia de enteros llamado exponente. La base es usualmente fija, y la mantisa
y el exponente varan para representar diferentes nmeros reales.

Si la base es 10, el numero 387.53 puede ser representado como 38753 X 10
-2
. La mantisa es
38753 y el exponente es -2.

En los nmeros reales representados por 32 bits, la cadena consistente de 24 bits representan la
mantisa y los siguientes 8 bits el exponente y la base es fija a 10. Tanto la mantisa como el
exponente son enteros binarios de complemento 2 de esta forma:

Por ejemplo:

24 bits representan el 38753 es decir, 000000001001011101100001
8 bits en complemento a 2 representan -2 como 11111110
Por lo tanto 387.53 es representado como 00000000100101110110000111111110 en 32 bits.

Cadenas de caracteres

No toda la informacin es representada de manera numrica, tambin es necesario representar
informacin referente a caracteres, en tal caso la informacin necesariamente es representada
como una cadena de caracteres. En un principio 8 bits eran usados para representar un carcter,
hasta 256 caracteres podan ser representados, a este formato de caracteres se le denomino
cdigo ASCII y era capas de representar la totalidad de caracteres de un alfabeto occidental. En la
actualidad es utilizado sistema UNICODE, que es capas de representar hasta 2
16
(65536). La
mayora de los sistemas actuales sin capaces de diferenciar entre un cdigo ASCII y UNICODE.

UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
4
1.3 Estructura de datos

Una herramienta til para especificar las propiedades lgicas de un tipo de dato abstracto.
Fundamentalmente, un tipo de dato es una coleccin de valores y un conjunto de operaciones
sobre esos valores, y forman una construccin matemtica que podra ser implementada usando
un hardware particular o una estructura de datos en software. El termino tipo de dato abstracto se
refiere al concepto matemtico bsico que define el tipo de dato.

Existen numerosos mtodos para especificar tipos de dato abstractos. Hasta el momento hemos
explicado algunos tipos de datos, que denominamos nativos, como son los enteros, reales, y
caracteres. En lenguajes de alto nivel podemos encontrar tipos de datos como los apuntadores,
que son identificadores de referencia a una localidad de memoria y que pueden almacenar tipos de
datos nativos, as como tipos de dato abstracto.

Por lo tanto el estudio de estructura de datos involucra dos objetivos complementarios:

El primero es identificar y desarrollar entidades matemticas tiles y operaciones para
determinar que clases de problemas pueden ser resueltos por el uso de estas entidades y
operaciones.

El segundo es determinar representaciones para estas entidades abstractas e implementar
operaciones abstractas en estas representaciones concretas.

En el primero de estos objetivos se ve al tipo de dato de alto nivel como una herramienta que
puede ser usada para solucionar otros problemas, y en la segunda, la implementacin de tales
tipos de datos se ve como un problema a ser resuelto usando los tipos de datos ya existentes.

Inicialmente examinaremos varias estructuras de datos ya existentes en C, como son los arreglos y
las estructuras. Describiremos los servicios que estn disponibles en C para utilizar estas
estructuras, adems de enfocarnos en la definicin abstracta de estas estructuras de datos y como
ellas pueden ser tiles en problemas a resolver.

Mas adelante se desarrollaran estructuras de datos mas complejas y veremos su utilidad en la
solucin de problemas, adems veremos como implementar estas estructuras de datos usando las
estructuras ya disponibles en C.
1.4 Arreglos

La forma ms simple de arreglo es el arreglo de una dimensin, que podra ser definido de manera
abstracta como un conjunto ordenado de elementos homogneos de tamao finito.

Posicin individual A [ ndice 0 ] A [ ndice 1 ] ... A [ ndice i-simo ]

Arreglo elemento 0 elemento 1 ... elemento i-simo

Por ejemplo, en C:

<tipo> vector[100]; // arreglo de 100 posiciones de enteros

Las dos operaciones bsicas que se pueden realizar en un arreglo son la extraccin y el
almacenamiento. La operacin de extraccin es una funcin que acepta un arreglo y un ndice, y
regresa el elemento del arreglo. En C esta operacin es denotada como:

vector [ ndice ]

UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
5
La operacin de almacenamiento acepta un elemento x en un ndice cualquiera del arreglo:

vector [ ndice ] = x

En los vectores existe un limite inferior (0 en C, debido a que se expresa como desplazamientos de
memoria) que es la primera posicin del arreglo y un limite superior (n-1 en el caso de C) que es el
ultimo elemento del arreglo.

Los arreglos son usados cuando es necesario mantener una gran numero de elementos en
memoria y referenciar todos sus elementos de manera uniforme. A continuacin se presenta un
ejemplo en C++ que permite crear arreglos de manera dinmica y se emplean algunas funciones
que permiten manipular su contenido.

#include <iostream>
using namespace std;

int * crea( int & ); // crea el vector dinamico
void captura( int [], int ); // captura los datos
void mostrar( const int *, int ); // muestra el contenido

int main(void)
{
int *vector; // definimos un puntero de tipo int
int num;

vector = crea(num);
captura(vector, num);
mostrar(vector, num);
cin.ignore();
cin.get();
return 0;
}

/* La funcion es de tipo int * ya que pretendemos regresar la
direccion de memoria disponoble para el vector, el operador &
indica paso de parametro por referencia
*/
int * crea( int &n )
{
int *ptr;
cout << "cuantos elementos ? ";
cin >> n;
ptr = new int [n];
return ptr; // regresa la direccion de inicio del vector dinamico
}

/* captura, recibe el vector dinamico y su dimension, tome en
cuanta que los valores contenidos en vector seran modificados
mediante vec. El argumento n es pasado por valor.
*/
void captura( int vec [], int n )
{
for( int i=0; i < n; i++ )
{
cout << " numero -> ";
cin >> vec[i];
}
UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
6
return;
}

/* el primer argumento se declara tipo const (constante) lo que
impide que los valores del vector sean modificados.
*/
void mostrar(const int * vec, int n)
{
cout <<"Contenido del vector..."<<endl;
for (int i=0; i < n; i++)
{
cout << vec[i] <<'\t';
}
cout << endl;
return;
}

En el caso de los arreglos de caracteres (strings) es importante recordar que el carcter \0 es
utilizado para representar el fin del arreglo y ocupa una posicin real dentro del mismo. Tambin es
importante tener en mente que en C se pueden crear arreglos de tamao variable empleando
punteros. A continuacin se muestra un ejemplo del manejo de arreglos de caracteres empleando
el carcter de control para manejar su limite.

#include <iostream>
using namespace std;
int nveces( char *, char * );

int main()
{
char *s1 = "si", *s = "si y solo si, si?";

cout << nveces(s1, s);
cin.get();
return 0;
}

int nveces( char *s1, char *s )
{
int n = 0, i = 0, j = 0;
bool c = false;
while ( s[j] != '\0' ){
for ( i = 0; s1 [i] != '\0'; )
c = (s1[i++] == s[j++])? true: false;
/* Esto es mas eficiente pues una vez que no hay
concordancia ya no continua checando y rompe el for
if ( s1[i++] == s[j++])
c = true;
else {
c = false;
break; // aqui
}
*/
n += (c == true)? 1: 0;
}
return n;
}

UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
7
Los arreglos en C pueden tener mltiples ndices y son mas comnmente conocidos como
matrices. La matriz es un arreglo bidimensional organizado como una tabla, donde los datos se
encuentran organizados en filas y columnas:

columna c columna c +1 columna columna m
fila f elemento(f , c) elemento(f , c+1) elemento( f, m )
fila f + 1 elemento(f +1 , c ) elemento(f +1 , c +1) elemento(f +1 , m )
fila
fila n elemento(n , c ) elemento(n , c +1) elemento(n , m)

Las matrices se declaran de forma muy similar a los vectores, nicamente se agrega una
dimensin, recuerde que el lenguaje C tambin permite agregar ms dimensiones a los arreglos:

<Tipo> nombre_matriz [ filas ] [ columnas ];

A continuacin se presenta una forma de asignar matrices dinamicas en C:

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
int i, j;

int **p = calloc(2, sizeof(int *));
for( i = 0; i < 3; i++)
p[i] = calloc(3, sizeof(int));

for ( i = 0; i < 2; i++)
for ( j = 0; j < 3; j++)
p[i][j] = i + j;

for ( i = 0; i < 2; i++)
for ( j = 0; j < 3; j++)
printf( "%d %p ",p[i][j], &p[i][j] );

return 0;
}

Un esquema similar se puede emplear en C++
#include <iostream>
using namespace std;

int main()
{
short **matriz; // declara un puntero puntero
int rens, cols; // numero de renglones y columnas

cout << "numero de renglones ";
cin >> rens;
cout << "numero de columnas ";
cin >> cols;
cout << " Matriz de "<<rens << " por " << cols << endl;

// asigna espacio para crear un vector de punteros
matriz = new short *[rens];
UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
8

for(int i = 0; i < rens; i++)
{
/* a cada casilla del vector de punteros se le asigna una
cantidad de columnas de tipo short */
matriz[i] = new short [cols];

for(int j = 0; j < cols; j++)
{ // ingresamos un elemento para la matriz
cout << "elemento short "<<i<<","<<j<<" ";
cin >> matriz[i][j]; /* finalmente la matriz puede ser
usada como cualquier matriz */
}
}
cout << "\nLa matriz contiene:\n";
for(int i = 0; i < rens; i++) {
for(int j = 0; j < cols; j++)
cout << matriz[i][j] << " ";
cout << "\n";
}
cin.ignore();
cin.get();
return 0;
}

1.5 Estructuras (registros)

Una estructura es una forma de agrupar un conjunto de datos de distinto tipo bajo un mismo
nombre o identificador.

Por ejemplo:

struct Estructura {
<tipo> miembro1;
<tipo> miembro2;
...
};

Estructura corresponde al identificador con el que nos referimos a la estructura, adems existen los
miembros o variables que estn asociadas bajo el mismo nombre de la estructura. De esta manera
podemos generar variables de tipo Estructura, a las variables de este tipo las denominaremos
registros, por ejemplo generamos el registro llamado Datos;

struct Estructura Datos;

Dentro del registro existen miembros y peden ser alcanzados con el operador de acceso a
miembros:

Datos.miembro1 = valor

Los miembros de las estructuras pueden ser variables de cualquier tipo, incluyendo punteros,
vectores, matrices, e incluso estructuras previamente definidas.

Las estructuras constituyen uno de los aspectos ms potentes del lenguaje C. En esta seccin se
ha tratado slo de hacer una breve presentacin. C++ generaliza este concepto incluyendo
UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
9
funciones miembro adems de variables miembro, llamndolo clase, y convirtindolo en la base de
la programacin orientada a objetos.

A continuacin se presenta un programa con manejo de estructuras que captura y manipula un
arreglo de estructuras de un tipo de dato definido (Alumno), observe que el manejo eficiente de
estructuras siempre reducir el nivel de complejidad de un problema:

#include <iostream>
using namespace std;
#include <conio2.h>

struct Alumno // estructura Alumno, conserva datos de alumno, as como
{
int id;
char nombre[30];
char carrera[30];
struct Materia // conserva los datos asociados a todas
{ // las materias de un alumno
char nombre[30];
float calificacion;
}materias[2]; //vector tipo Materia almacenamos cada materia
};

void captura( Alumno * ); // Captura los datos de cada alumno
void imprime( Alumno * ); // imprime el contenido del vector
void verVector( Alumno [] , int );

int main()
{
Alumno *al = new Alumno; /* estructura dinamica */
int n; // tamao del vector dinamico grupo

cout << "Numero de alumnos -> ";
cin >> n;
Alumno *grupo = new Alumno [n]; // vector dinamico grupo

for (int cont = 0; cont < n; cont++){
captura(al); //captura datos de cada alumno en la estructura
grupo[cont] = *al;
}
for (int i=0; i < n; i++){
*al = grupo[i]; // vacia el contenido de grupo[i] en al
imprime( al );
}
clrscr();

verVector( grupo, n );

cin.ignore();
cin.get();
delete [] grupo;
delete al;
return 0;
}

/* captura la informacion de cada alumno, recibe la referencia de una
estructura Alumno que sera modificada localmente en la funcion */
UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
10
void captura(Alumno *al) {
cout << "\tDatos del alumno" << endl << "Clave: ";
cin >> al->id;
cout << "Nombre: ";
cin.ignore();
cin.get(al->nombre,30);
cout << "Carrera: ";
cin.ignore();
cin.get(al->carrera,30);
cout << "\tCaptura de Materias\n";
for(int i=0; i < 2; i++) {
cout <<"Materia "<<(i+1)<<": ";
cin.ignore();
cin.get(al->materias[i].nombre,30);
cout << "Calificacion: ";
cin >> al->materias[i].calificacion;
}
return;
}

/* imprime la informacion de cada alumno, recibe la referencia de una
estructura Alumno que sera modificada localmente en la funcion */
void imprime( Alumno *al )
{
cout<<"\tReporte Datos del alumno\n Clave:"<< al->id;
/* referencia valida a miembros para una estructura tipo puntero */
cout << "\n Nombre: " << (*al).nombre; /* referencia valida a
miembros para una estructura tipo puntero */
cout << "\n Carrera: " << al->carrera;
// ambos tipos de referencia a miembros ( -> y *. ) son validas
cout << "\n\tMaterias del Alumno\n";
for(int i=0; i < 2; i++) {
cout <<"Materia " << (i+1)<<": " << al->materias[i].nombre;
cout << "\tCalificacion: " << al->materias[i].calificacion;
cout << endl;
}
return;
}

/* recibe como argumento la direccion del arreglo completo de alumnos,
es decir el grupo, de manera que es posible tener acceso a todos los
elementos del arreglo */
void verVector( Alumno grp[], int n )
{
cout << "impresion de grupo \n";
for ( int i = 0; i < n; i++ ){
cout << "id " << grp[i].id << endl;
cout << "nombre " << grp[i].nombre << endl;
cout << "carrera " << grp[i].carrera << endl;
for ( int j = 0; j < 2; j++ ){
cout << "materia " << grp[i].materias[j].nombre << endl;
cout<<"calificacion" << grp[i].materias[j].calificacion << endl;
}
cout << endl;
}
return;
}
UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
11
1.6 Clases

Una clase engloba el concepto de tipo de dato abstracto ya que define tanto el conjunto de
valores de un tipo dado, como el conjunto de operaciones que pueden realizarse con esos valores.
Una variable de tipo clase es conocida como objeto y las operaciones que ese tipo realiza son
llamadas mtodos. Se dice que los objetos envan mensajes entre ellos a partir de dichos mtodos.

class Nombre {
<tipo> elemento;
<tipo> elemento;
...
<tipo> mtodo(lista de argumentos);
<tipo> mtodo(lista de argumentos);
...
}

Las reglas por las se rige el acceso a los miembros es identica a las estructuras, en cierta forma a
las clases se les puede ver como una estructura evolucionada que permite realizar operaciones
limpiamente sobre sus datos.

#include <iostream>
using namespace std;

class Juego
{
private:
int rango, num, intento, numIntentos;

private:
// genera un numero aleatorio entre 0 y limite
int Juego::random(int limite) { return rand() % limite; }

public:
Juego::Juego( int Rango = 31, int Intentos = 5 )
{
rango = Rango;
srand( time(NULL) ); // semilla de numeros aleatorios variables
num = random(rango); // extrae el numero aleatorio
intento = 0;
numIntentos = Intentos;
}

bool Juego::acierta()
{
int val = 0;
do {
cout << " adivina el numero entre (0..30)?, intento " <<
++intento << " ";
cin >> val;
if ( val > num ) // si es mayor le decimos que le baje
cout << "es menor\n";
else if ( val < num ) // si es menor le decimos que le suba
cout << "es mayor\n";
// mientras no sea el numero y los intentos sean menos de
numIntentos
} while ( val != num && intento < numIntentos );
if (val != num)
UAA Sistemas Electrnicos Estructura de Datos Muoz / Serna
12
return false;
else
return true;
}
int Juego::intentos() { return intento; }

int Juego::NumIntentos() { return numIntentos; }
};

int main()
{
int limNum, limInt;
cout << "generar numeros entre 0 y ? ";
cin >> limNum;
cout << "cuantos intentos maximos ? ";
cin >> limInt;

Juego obj(limNum, limInt);

if ( obj.acierta() )
cout << " Acerto !!! ";
else
cout << " Fallo!!! ";
cout << " intentos " << obj.intentos() << " de "
<< obj.NumIntentos() << " \n";
system("pause");
return 0;
}


Bibliografa

Y. Langsam, M. J. Augenstein, A. Tenenbaum. Data Structures using C and C++. Prentice Hall,
Second edition. ISBN 0-13-036997-7.