Академический Документы
Профессиональный Документы
Культура Документы
Listas
Fundamentos
Las listas son secuencias de elementos, donde estos elementos pueden ser
accedidos, insertados o suprimidos en cualquier posicin de la lista. No existe
restriccin alguna acerca de la localizacin de esas operaciones. Se trata de
estructuras muy flexibles puesto que pueden crecer o acotarse como se quiera.
Matemticamente, una lista es una secuencia de cero o ms elementos de un tipo
determinado (que por lo general denominaremos T). A menudo se representa una
lista como una sucesin de elementos separados por comas:
donde a "n" (n >= 0) se le llama longitud de la lista. Al suponer n>=1, se dice que
a(1) es el primer elemento de la lista y a(n) el ltimo elemento. Si n=0, se tiene
una lista vaca.
Una propiedad importante de una lista es que sus elementos pueden estar
ordenados en forma lineal de acuerdo con sus posiciones en la lista. Se dice que
a(i) precede a a(i+1), para i=1,2, .., n-1, y que a(i) sucede a a(i-1), para i=2, 3, .., n.
Tambin se dice que el elemento a(i) est en la posicin i de la lista.
Por lo que se ve, las estructuras pila y cola, no son ms que casos particulares de
la estructura lista generalizada. Al igual que en los casos anteriores, se puede
pensar en representar una lista mediante un array unidimensional, lo que permite
un acceso eficiente a cada uno de los componentes de la estructura, lo que, en
principio, parece proporcionar un esquema adecuado para representar las
operaciones que normalmente se desean realizar sobre la lista: acceder a un
elemento (nodo) arbitrario de la lista, insertar y borrar nodos, etc. Sin embargo, si
bien todas estas consideraciones eran ciertas para las pilas y las colas, cuando se
trata de otro tipo de listas, las operaciones a realizar sobre el array resultan
bastante ms costosas. Por ejemplo, supongamos la siguiente lista:
(Antonio, Bartolom, Carlos, David, Emilio, Germn, Jaime, Jos,
Luis, Manuel)
Listas enlazadas
Cuando se utilizan representaciones enlazadas para listas, se est rompiendo la
asociacin que exista entre la secuencia lgica establecida en la estructura de
datos y la secuencia fsica que representaba esa lista en un array. Ahora, los
elementos de la lista pueden estar en zonas de memoria no necesariamente
contiguas, lo que tiene como ventaja que cuando se desee insertar un elemento
nuevo, no hay que hacerlo en una posicin concreta de la memoria y que, por lo
tanto, no hay que desplazar elementos. Lo mismo ocurrir con la operacin de
borrado, para eliminar un elemento bastar con aislarlo de la secuencia de orden
que sigue la lista, sin que por ello sea necesario mover ningn elemento.
El almacenamiento no secuencial de la informacin implica que para acceder a los
nodos de la lista en el orden correcto, es necesario almacenar junto a la
informacin propia de cada elemento la localizacin o direccin del siguiente
elemento de la lista, Por ejemplo, supongamos que la anterior lista ordenada de
nombres se almacena de forma no secuencial en la memoria, entonces su
esquema de almacenamiento sera anlogo al mostrado por la siguiente figura,
donde se asocia la estructura de la memoria con la de un array unidimensional.
1
2
3
4
5
6
7
8
9
...
----------
Antonio
Carlos
David
4
7
Emilio
15
Bartolom
...
3
...
El campo liga, que es de tipo puntero, es el que se usa para establecer la liga con
el siguiente nodo de la lista. Si el nodo fuera el ltimo, este campo recibe como
valor NIL (vaco).
A continuacin se muestra el esquema de una lista :
Listas abiertas
1.1 Definicin:
La forma ms simple de estructura dinmica es la lista abierta. En esta forma los
nodos se organizan de modo que cada uno apunta al siguiente, y el ltimo no
apunta a nada, es decir, el puntero del nodo siguiente vale NULL.
En las listas abiertas existe un nodo especial: el primero. Normalmente diremos
que nuestra lista es un puntero a ese primer nodo y llamaremos a ese nodo la
cabeza de la lista. Eso es porque mediante ese nico puntero podemos acceder a
toda la lista.
Cuando el puntero que usamos para acceder a la lista vale NULL, diremos que la
lista est vaca.
El nodo tpico para construir listas tiene esta forma:
struct nodo {
int dato;
struct nodo *siguiente;
};
En el ejemplo, cada elemento de la lista slo contiene un dato de tipo entero, pero
en la prctica no hay lmite en cuanto a la complejidad de los datos a almacenar.
1.2 Declaraciones de tipos para manejar listas en C:
Normalmente se definen varios tipos que facilitan el manejo de las listas, en C, la
declaracin de tipos puede tener una forma parecida a esta:
10
Es muy importante que nuestro programa nunca pierda el valor del puntero al
primer elemento, ya que si no existe ninguna copia de ese valor, y se pierde, ser
imposible acceder al nodo y no podremos liberar el espacio de memoria que
ocupa.
1.3 Operaciones bsicas con listas:
Con las listas tendremos un pequeo repertorio de operaciones bsicas que se
pueden realizar:
Cada una de estas operaciones tendr varios casos especiales, por ejemplo, no
ser lo mismo insertar un nodo en una lista vaca, o al principio de una lista no
vaca, o la final, o en una posicin intermedia.
1.4 Insertar elementos en una lista abierta:
Veremos primero los casos sencillos y finalmente construiremos un algoritmo
genrico para la insercin de elementos en una lista.
11
12
Suponemos que ya disponemos del nuevo nodo a insertar, apuntado por nodo, y
un puntero al nodo a continuacin del que lo insertaremos.
El proceso a seguir ser:
1. Hacer que nodo->siguiente seale a anterior->siguiente.
2. Hacer que anterior->siguiente seale a nodo.
sentido, ya que cada nodo apunta al siguiente, pero no se puede obtener, por
ejemplo, un puntero al nodo anterior desde un nodo cualquiera si no se empieza
desde el principio.
Para recorrer una lista procederemos siempre del mismo modo, usaremos un
puntero auxiliar como ndice:
1. Asignamos al puntero ndice el valor de Lista.
2. Abriremos un bucle que al menos debe tener una condicin, que el ndice
no sea NULL.
3. Dentro del bucle asignaremos al ndice el valor del nodo siguiente al ndice
actual.
Por ejemplo, para mostrar todos los valores de los nodos de una lista, podemos
usar el siguente bucle en C:
...
indice = Lista;
while(indice && indice->dato <= 100) {
printf("%d\n", indice->dato);
14
indice = indice->siguiente;
}
...
Si analizamos la condicin del bucle, tal vez encontremos un posible error: Qu
pasara si ningn valor es mayor que 100, y alcancemos el final de la lista?. Podra
pensarse que cuando indice sea NULL, si intentamos acceder a indice->dato se
producir un error.
En general eso ser cierto, no puede accederse a punteros nulos. Pero en este
caso, ese acceso est dentro de una condicin y forma parte de una expresin
"and". Recordemos que cuando se evala una expresin "and", se comienza por
la izquierda, y la evaluacin se abandona cuando una de las expresiones resulta
falsa, de modo que la expresin "indice->dato <= 100" nunca se evaluar si indice
es NULL.
Si hubiramos escrito la condicin al revs, el programa nunca funcionara bien.
Esto es algo muy importante cuando se trabaja con punteros.
1.6 Eliminar elementos en una lista abierta:
De nuevo podemos encontrarnos con varios casos, segn la posicin del nodo a
eliminar.
Eliminar el primer nodo de una lista abierta:
Es el caso ms simple. Partiremos de una lista con uno o ms nodos, y usaremos
un puntero auxiliar, nodo:
15
16
17
una lista vaca, y haremos que la funcin para insertar en la primera posicin nos
sirva para ese caso tambin.
Algoritmo de insercin:
1. El primer paso es crear un nodo para el dato que vamos a insertar.
2. Si Lista es NULL, o el valor del primer elemento de la lista es mayor que el
del nuevo, insertaremos el nuevo nodo en la primera posicin de la lista.
3. En caso contrario, buscaremos el lugar adecuado para la insercin,
tenemos un puntero "anterior". Lo inicializamos con el valor de Lista, y
avanzaremos mientras anterior->siguiente no sea NULL y el dato que
contiene anterior->siguiente sea menor o igual que el dato que queremos
insertar.
4. Ahora ya tenemos anterior sealando al nodo adecuado, as que
insertamos el nuevo nodo a continuacin de l.
18
19
#include <stdlib.h>
#include <stdio.h>
typedef struct _nodo {
int valor;
struct _nodo *siguiente;
} tipoNodo;
typedef tipoNodo *pNodo;
typedef tipoNodo *Lista;
/* Funciones con listas: */
void Insertar(Lista *l, int v);
void Borrar(Lista *l, int v);
int ListaVacia(Lista l);
void BorrarLista(Lista *);
void MostrarLista(Lista l);
int main() {
Lista lista = NULL;
20
pNodo p;
Insertar(&lista,
Insertar(&lista,
Insertar(&lista,
Insertar(&lista,
20);
10);
40);
30);
MostrarLista(lista);
Borrar(&lista,
Borrar(&lista,
Borrar(&lista,
Borrar(&lista,
Borrar(&lista,
10);
15);
45);
30);
40);
MostrarLista(lista);
BorrarLista(&lista);
getchar();
return 0;
}
void Insertar(Lista *lista, int v) {
pNodo nuevo, anterior;
/* Crear un nodo nuevo */
nuevo = (pNodo)malloc(sizeof(tipoNodo));
nuevo->valor = v;
/* Si la lista est vaca */
if(ListaVacia(*lista) || (*lista)->valor > v) {
/* Aadimos la lista a continuacin del nuevo
nodo */
nuevo->siguiente = *lista;
/* Ahora, el comienzo de nuestra lista es en
nuevo nodo */
*lista = nuevo;
}
21
else {
/* Buscar el nodo de valor menor a v */
anterior = *lista;
/* Avanzamos hasta el ltimo elemento o hasta
que el siguiente tenga
un valor mayor que v */
while(anterior->siguiente && anterior>siguiente->valor <= v)
anterior = anterior->siguiente;
/* Insertamos el nuevo nodo despus del nodo
anterior */
nuevo->siguiente = anterior->siguiente;
anterior->siguiente = nuevo;
}
}
void Borrar(Lista *lista, int v) {
pNodo anterior, nodo;
nodo = *lista;
anterior = NULL;
while(nodo && nodo->valor < v) {
anterior = nodo;
nodo = nodo->siguiente;
}
if(!nodo || nodo->valor != v) return;
else { /* Borrar el nodo */
if(!anterior) /* Primer elemento */
*lista = nodo->siguiente;
else /* un elemento cualquiera */
anterior->siguiente = nodo->siguiente;
free(nodo);
}
}
int ListaVacia(Lista lista) {
return (lista == NULL);
}
22
while(*lista) {
nodo = *lista;
*lista = nodo->siguiente;
free(nodo);
}
Listas circulares
2.1 Definicin
Una lista circular es una lista lineal en la que el ltimo nodo a punta al primero.
Las listas circulares evitan excepciones en la operaciones que se realicen sobre
ellas. No existen casos especiales, cada nodo siempre tiene uno anterior y uno
siguiente.
En algunas listas circulares se aade un nodo especial de cabecera, de ese modo
se evita la nica excepcin posible, la de que la lista est vaca.
El nodo tpico es el mismo que para construir listas abiertas:
23
struct nodo {
int dato;
struct nodo *siguiente;
};
2.2 Declaraciones de tipos para manejar listas circulares en C:
Los tipos que definiremos normalmente para manejar listas cerradas son los
mismos que para para manejar listas abiertas:
A pesar de que las listas circulares simplifiquen las operaciones sobre ellas,
tambin introducen algunas complicaciones. Por ejemplo, en un proceso de
bsqueda, no es tan sencillo dar por terminada la bsqueda cuando el elemento
buscado no existe.
Por ese motivo se suele resaltar un nodo en particular, que no tiene por qu ser
siempre el mismo. Cualquier nodo puede cumplir ese propsito, y puede variar
durante la ejecucin del programa.
24
Otra alternativa que se usa a menudo, y que simplifica en cierto modo el uso de
listas circulares es crear un nodo especial de har la funcin de nodo cabecera.
De este modo, la lista nunca estar vaca, y se eliminan casi todos los casos
especiales.
2.3 Operaciones bsicas con listas circulares:
A todos los efectos, las listas circulares son como las listas abiertas en cuanto a
las operaciones que se pueden realizar sobre ellas:
Cada una de estas operaciones podr tener varios casos especiales, por ejemplo,
tendremos que tener en cuenta cuando se inserte un nodo en una lista vaca, o
cuando se elimina el nico nodo de una lista.
2.4 Aadir un elemento:
El nico caso especial a la hora de insertar nodos en listas circulares es cuando la
lista est vaca.
Aadir elemento en una lista circular vaca:
Partiremos de que ya tenemos el nodo a insertar y, por supuesto un puntero que
apunte a l, adems el puntero que define la lista, que valdr NULL:
25
26
27
1.
2.
3.
4.
Este mtodo tambin funciona con listas circulares de un slo elemento, salvo que
el nodo a borrar es el nico nodo que existe, y hay que hacer que lista apunte a
NULL.
2.7 Ejemplo de lista circular en C:
Construiremos una lista cerrada para almacenar nmeros enteros. Haremos
pruebas insertando varios valores, buscndolos y eliminndolos alternativamente
para comprobar el resultado.
Algoritmo de la funcin "Insertar":
1. Si lista est vaca hacemos que lista apunte a nodo.
2. Si lista no est vaca, hacemos que nodo->siguiente apunte a lista>siguiente.
3. Despus que lista->siguiente apunte a nodo.
28
29
}
else {
// Si la lista tiene ms de un nodo, borrar
el nodo de valor v
nodo = (*lista)->siguiente;
(*lista)->siguiente = nodo->siguiente;
free(nodo);
}
}
}
Cdigo del ejemplo completo:
Tan slo nos queda escribir una pequea prueba para verificar el funcionamiento:
#include <stdio.h>
typedef struct _nodo {
int valor;
struct _nodo *siguiente;
} tipoNodo;
typedef tipoNodo *pNodo;
typedef tipoNodo *Lista;
// Funciones con listas:
void Insertar(Lista *l, int v);
void Borrar(Lista *l, int v);
void BorrarLista(Lista *);
void MostrarLista(Lista l);
int main() {
Lista lista = NULL;
pNodo p;
Insertar(&lista, 10);
Insertar(&lista, 40);
Insertar(&lista, 30);
30
Insertar(&lista, 20);
Insertar(&lista, 50);
MostrarLista(lista);
Borrar(&lista, 30);
Borrar(&lista, 50);
MostrarLista(lista);
BorrarLista(&lista);
getchar();
return 0;
}
void Insertar(Lista *lista, int v) {
pNodo nodo;
// Creamos un nodo para el nuvo valor a insertar
nodo = (pNodo)malloc(sizeof(tipoNodo));
nodo->valor = v;
// Si la lista est vaca, la lista ser el nuevo
nodo
// Si no lo est, insertamos el nuevo nodo a
continuacin del apuntado
// por lista
if(*lista == NULL) *lista = nodo;
else nodo->siguiente = (*lista)->siguiente;
// En cualquier caso, cerramos la lista circular
(*lista)->siguiente = nodo;
}
void Borrar(Lista *lista, int v) {
pNodo nodo;
nodo = *lista;
31
32
}
void MostrarLista(Lista lista) {
pNodo nodo = lista;
do {
printf("%d -> ", nodo->valor);
nodo = nodo->siguiente;
} while(nodo != lista);
printf("\n");
struct nodo {
int dato;
struct nodo *siguiente;
struct nodo *anterior;
};
3.2 Declaraciones de tipos para manejar listas doblemente enlazadas en C:
Para C, y basndonos en la declaracin de nodo que hemos visto ms arriba,
trabajaremos con los siguientes tipos:
33
34
El proceso es el siguiente:
1. nodo->siguiente debe apuntar a Lista.
2. nodo->anterior apuntar a Lista->anterior.
3. Lista->anterior debe apuntar a nodo.
35
Recuerda que Lista no tiene por qu apuntar a ningn miembro concreto de una
lista doblemente enlazada, cualquier miembro es igualmente vlido como
referencia.
Insertar un elemento en la ltima posicin de la lista:
Igual que en el caso anterior, partiremos de una lista no vaca, y de nuevo para
simplificar, que Lista est apuntando al ltimo elemento de la lista:
El proceso es el siguiente:
1. nodo->siguiente debe apuntar a Lista->siguiente (NULL).
2. Lista->siguiente debe apuntar a nodo.
3. nodo->anterior apuntar a Lista.
36
Lo que hemos hecho es trabajar como si tuviramos dos listas enlazadas, los dos
primeros pasos equivalen a lo que hacamos para insertar elementos en una lista
abierta corriente.
Los dos siguientes pasos hacen lo mismo con la lista que enlaza los nodos en
sentido contrario.
El paso 4 es el ms oscuro, quizs requiera alguna explicacin.
Supongamos que disponemos de un puntero auxiliar, "p" y que antes de empezar
a insertar nodo, hacemos que apunte al nodo que quedar a continuacin de nodo
despus de insertarlo, es decir p = Lista->siguiente.
Ahora empezamos el proceso de insercin, ejecutamos los pasos 1, 2 y 3. El
cuarto sera slo hacer que p->anterior apunte a nodo. Pero nodo->siguiente ya
apunta a p, as que en realidad no necesitamos el puntero auxiliar, bastar con
hacer que nodo->siguiente->anterior apunte a nodo.
Aadir elemento en una lista doblemente enlazada, caso general:
Para generalizar todos los casos anteriores, slo necesitamos aadir una
operacin:
1. Si lista est vaca hacemos que Lista apunte a nodo. Y nodo->anterior y
nodo->siguiente a NULL.
2. Si lista no est vaca, hacemos que nodo->siguiente apunte a Lista>siguiente.
3. Despus que Lista->siguiente apunte a nodo.
4. Hacemos que nodo->anterior apunte a Lista.
5. Si nodo->siguiente no es NULL, entonces hacemos que nodo->siguiente>anterior apunte a nodo.
El paso 1 es equivalente a insertar un nodo en una lista vaca.
37
38
Para los casos que lo permitan consideraremos dos casos: que el nodo a eliminar
es el actualmente apuntado por Lista o que no.
1. Eliminamos el nodo.
39
40
41
42
43
#include <stdio.h>
#define ASCENDENTE 1
#define DESCENDENTE 0
typedef struct _nodo {
int valor;
44
20);
10);
40);
30);
MostrarLista(lista, ASCENDENTE);
MostrarLista(lista, DESCENDENTE);
Borrar(&lista,
Borrar(&lista,
Borrar(&lista,
Borrar(&lista,
10);
15);
45);
30);
MostrarLista(lista, ASCENDENTE);
MostrarLista(lista, DESCENDENTE);
BorrarLista(&lista);
getchar();
return 0;
45
46
47
}
*lista = NULL;
printf("\n");
48
CONCLUSION
Una de las aplicaciones ms interesantes y potentes de la memoria dinmica y los
punteros son las estructuras dinmicas de datos. Las estructuras bsicas
disponibles en C y C++ tienen una importante limitacin: no pueden cambiar de
tamao durante la ejecucin. Los arreglos estn compuestos por un determinado
nmero de elementos, nmero que se decide en la fase de diseo, antes de que el
programa ejecutable sea creado.
En muchas ocasiones se necesitan estructuras que puedan cambiar de tamao
durante la ejecucin del programa. Por supuesto, podemos hacer 'arrays'
dinmicos, pero una vez creados, tu tamao tambin ser fijo, y para hacer que
crezcan o diminuyan de tamao, deberemos reconstruirlas desde el principio.
Las estructuras dinmicas nos permiten crear estructuras de datos que se adapten
a las necesidades reales a las que suelen enfrentarse nuestros programas. Pero
no slo eso, como veremos, tambin nos permitirn crear estructuras de datos
muy flexibles, ya sea en cuanto al orden, la estructura interna o las relaciones
entre los elementos que las componen.
Las estructuras de datos estn compuestas de otras pequeas estructuras a las
que llamaremos nodos o elementos, que agrupan los datos con los que trabajar
49
struct nodo {
int dato;
struct nodo *otronodo;
};
El campo "otronodo" puede apuntar a un objeto del tipo nodo. De este modo, cada
nodo puede usarse como un ladrillo para construir listas de datos, y cada uno
mantendr ciertas relaciones con otros nodos.
Para acceder a un nodo de la estructura slo necesitaremos un puntero a un nodo.
Durante el presente curso usaremos grficos para mostrar la estructura de las
estructuras de datos dinmicas. El nodo anterior se representar as:
Los otros tipos de estructuras dinmicas son: Listas, Colas, rboles, rboles
Binarios, rboles Binarios de Bsqueda, rboles AVL, rboles B, Tablas HASH,
Grafos y Diccionarios, entre otros.
50
Tambin existen estructuras dinmicas en las que existen nodos de distintos tipos,
en realidad no es obligatorio que las estructuras dinmicas estn compuestas por
un nico tipo de nodo, la flexibilidad y los tipos de estructuras slo estn limitados
por tu imaginacin como programador.
BIBLIOGRAFIA
Estructuras dinmicas de datos. Algoritmos, acceso, propiedades, ejemplos.
Direccin Pgina Web: http://c.conclase.net/edd/index.php?cap=000
INSTITUTO TECNOLOGICO de La Paz. Tutorial de Estructura de Datos.
Direccin Pgina Web: http://www.itlp.edu.mx/publica/tutoriales/estru1/
ALGORITMIA ALGO+ - Algoritmos y Estructuras de Datos.
Direccin Pgina Web: http://www.algoritmia.net/articles.php?id=13
100cia.com > Portada > Enciclopedia > Estructura_de_datos.
Direccin Pgina Web: http://www.100cia.com/enciclopedia/Estructura_de_datos
El Rincn del Programador. Programacin Genrica. Estructura de Datos. Listas.
Direccin Pgina Web:
http://rinconprog.metropoliglobal.com/CursosProg/ProgGen/Estr_Datos/index.php?
cap=4
51
52