Este documento describe las listas enlazadas como una estructura de datos flexible que permite agregar y eliminar nodos dinámicamente. Explica los tipos de listas, incluidas las listas simples y doblemente enlazadas, y define los niveles lógico e implementación de un TDA de lista enlazada simple. Además, detalla las operaciones básicas como crear, eliminar, insertar y eliminar nodos de una lista enlazada.
2. OBJETIVOS
Aprovechar la abstracción para definir
comportamiento y luego operaciones en nivel de
implementación
Visualizar las posibles implementaciones de un TDA ya
definido
Utilizar con seguridad el concepto de puntero en
implementaciones dinámicas
Reconocer la importancia de usar solo el
comportamiento de un TDA, y no su estado
3. LISTAS: DEFINICION
Una lista es
Una colección de 0 o mas elementos
Si la lista no tiene elementos, se dice que esta vacía
En una lista, todos los elementos son de un mismo tipo
Son estructuras lineales, es decir
Sus elementos están colocados uno detrás de otro
Cada elemento de una lista se conoce con el nombre de NODO
Las listas
Son mucho más flexibles que los arreglos
Permiten trabajo “dinámico” con un grupo de elementos
4. TIPOS
De acuerdo a su comportamiento, los conjuntos
lineales se clasifican en
Listas, Pilas y Colas
De acuerdo a su implementación, las listas se
clasifican en
Simples
Doblemente Enlazadas
Circulares
5. LISTAS SIMPLES
Se define como un conjunto de nodos
Uno detrás de otro
Del cual siempre se puede conocer al nodo inicial y al final
De cada nodo de la lista, se conoce
Un contenido, que es la información que almacena dentro
Puede ser de cualquier tipo de dato
Un sucesor único
Excepto el ultimo nodo de la lista
6. LISTA SIMPLE: NIVEL LOGICO
Comportamiento (a/con una lista se puede)
Crear y Eliminar
Conocer si esta vacía
Añadir elementos y removerlos
Consultar el primer y al ultimo elemento
Imprimir sus elementos en pantalla
Buscar un elemento con cierta información en la
lista
Estado:
<listaSimple> ::= <comienzo> + {<ref_nodo>} +
<final>
<comienzo> ::= <enlace>
<final> ::= <enlace>
7. TDA NODO: NIVEL LOGICO
Una lista esta compuesta de nodos, y por eso es importante
definir el TDA Nodo
Un nodo de una lista
Almacena información de cualquier tipo dentro y
Es capaz de “viajar” o conocer otro nodo(el nodo siguiente)
Comportamiento
Crear y Eliminar
Consultar y modificar la información que almacena
Consultar y modificar el enlace que mantiene con otro nodo
<nodo> ::= <contenido> + <enlace>
<contenido> ::= <dato>{<dato>}
<enlace> ::= <ref_nodo> | <ref_invalida>
8. LISTAS SIMPLES: NIVEL DE
IMPLEMENTACION
Tenemos el concepto claro de lo que debe ser una
lista
Ahora debemos ir al detalle: como darle vida a una
lista
Hay varias posibilidades de implementación
Estática o Contigua, usando arreglos de longitud “variable”
Dinámica, utilizando punteros
9. IMPLEMENTACION CONTIGUA
Se utilizan arreglos, por lo tanto
Tiene limites, que no pueden ser rebasados al añadir nuevo elementos
Los “nodos” son adyacentes en memoria
Cada nodo es realmente un elemento del arreglo
Entonces, el enlace con el siguiente nodo seria simplemente el indice del siguiente elemento dentro del arreglo
OJO: En este tipo de implementación no es necesario crear el TDA Nodo
Al crearla se debe indicar el tamaño máximo del arreglo
Al insertar o remover un elemento,
Todos los elementos restantes avanzarán o retrocederán
No es la implementacion ideal para las listas simples
10
0
5
1
8
2
2
3
31
4 5 6 7 8
En uso Desperdicio
25
Al insertarse un
nuevo elemento,
en una cierta
posición, todos los
elementos
restantes ruedan
25
3
2
4 5 6 7 8
31
10. CONTIGUAS: OPERACIONES
Creación y Eliminación
LSCont *LSCont_Crear(int n);
void LSCont_Vaciar(LSCont *L);
Añadir y Remover elementos
void LSCont_InsertarNodoInicio(LSCont *L, Generico
G);
void LSCont_InsertarNodoFin(LSCont *L, Generico
G);
Generico LSCont_SacarNodoInicio(LSCont *L);
Generico LSCont_SacarNodoFin(LSCont *L);
Consultar indice del Ultimo
int LSCont_ConsultarUltimo(LSCont L);
Consultar estado de la lista, puede llenarse
bool LSCont_EstaVacia(LSCont L);
bool LSCont_EstaLlena(LSCont L);
Busqueda y Recorrido
int LSCont_BuscarNodo(LSCont L, Generico
G, Generico_ComoComparar fn);
bool LSCont_EsNodoDeLista(LSCont L, int i);
void LSCont_Recorrer(LSCont L,
Generico_ComoImprimir fn);
11. CONTIGUAS: DECLARACION
La lista contigua
Es un arreglo de elementos de cualquier tipo realmente es un
ARRAYU
No olvidemos que hay que predefinir el máximo de nodos de la lista
Para lograr mantener control sobre la cantidad de elementos en la
lista
Debe existir una referencia que controle el ultimo elemento
La referencia al ultimo se moverá de acuerdo a las inserciones y
eliminaciones
La primero nunca se mueve, siempre será 0
typedef struct{
int ultimo;
ArrayU Elementos;
}LSCont;
10
0
5
1
8
2
25
3
2
4 5 6 7 8
31
header=0
MAX
Elementos
last
12. CONTIGUA: BASICAS
void LSCont_VaciarLista(LSCont *L){
L->ultimo = -1;
}
LSCont *LSCont_CrearLista(int max){
LSCont *nuevalista;
nuevalista->Elementos =
ArrayU_Crear(max, 0);
nuevalista->ultimo = -1;
return nuevalista;
}
void LSCont_EliminarLista(LSCont *L){
ArrayU_Eliminar(&(L->Elementos));
LSCont_VaciarLista(L);
}
bool LSCont_EstaVacia(LSCont L){
return(L.ultimo<0);
}
bool LSCont_EstaLlena(LSCont L){
return (L.ultimo ==
ArrayU_Tamanio(L.Elementol)-1);
}
0 1 2 3 4 5 6 7 8 9
last
El índice del último elemento
debe ser un índice inválido,
un valor negativo. No
importara que el arreglo
tenga elementos pues no
serán tomados en cuenta.
-1 0 1 2 3 4 5 6 7 8 9
last
MAX-1
Si la lista está llena es
porque el índice del último ha
llegado al verdadero último
índice posible en el arreglo:
MAX -1
14. CONTIGUA: INSERTAR
bool LSCont_Insertar(LSCont *L, int P, Generico G){
int i,
Generico ele1;
if(LSCont_EstaLlena(L)) return FALSE;
if(P<=-1) return FALSE;
//MOVER TODOS
for(i = L->ultimo; i >=P ;i--){
ele1 = ArrayU_ElemC(L->Elementos,i);
ArrayU_ElemM(L->Elementos,i+1, ele1);
}
ArrayU_ElemM(L->Elementos, P, G);
L->utlimo ++;
return TRUE;
}
bool LSCont_InsertarInicio(LSCont *L, Generico G){
int i;
Generico ele1, ele2;
//No insertar si ya esta llena
if(LSCont_EstaLlena(L)) return FALSE;
//Mover todo hacia delante
for(i = L->ultimo; i >=0 ;i--){
ele1 = ArrayU_ElemC(L->Elementos,i);
ArrayU_ElemM(L->Elementos,i+1, ele1);
}
ArrayU_ElemM(L->Elementos, 0, G);
L->ultimo++;
return(TRUE);
}
4 5 6 7 8 9
9
0 1 2
8
3
5
1
10
1 2 3
8
4
5
1
10
0
9
4 5 6 7 8 9
0 1 2
8
3
5
1
10
9
last last
last last
1 2 3
8
4
5
1
1
1
9
15. CONTIGUA: SACAR
bool LSCont_EliminarNodo(LSCont *L, int P){
int i;
Generico ele1;
if(LSCont_EstaVacia(L)) return FALSE;
if(P<=-1) return FALSE
//RETROCEDER TODOS
for(i = P; i < L->ultimo;i++){
ele1 = ArrayU_ElemC(L->Elementos, i+1);
ArrayU_ElemM(L->Elementos, i, ele1);
}
L->last --;
return TRUE;
}
bool LSCont_EliminarxInfo(LSCont *L, Generico G){
int pos;
if(LSCont_EstaVacia(L)) return FALSE;
pos = LSCont_Localizar(L, G);
if(pos >= 0)
return(LSCont_EliminaNodo(L, pos););
}
4 5 6 7 8 9
0 1 2
8
3
5
1
10
0 1 2
8
3
8
5
10
3 4 5 6 7 8
last
last
Eliminar por Info, dada
una cierta información,
buscar el elemento que
la tenga y eliminarlo
16. LSE: LISTAS SIMPLES ENLAZADAS
Es una implementación flexible y potente
Los nodos ya no son adyacentes en memoria
Un nodo A logra un enlace con otro B,
Almacenando dentro la dirección de memoria de B
Al insertar o eliminar un nodo ya no hay que “mover” al resto de
elemento, solo enlazarlo con la lista
10 5 8 2 31
25
25
Contenido Enlace
C S
2 31
25
C S
NODO A
NODO B
17. TDA LSE_NODO: NIVEL DE
IMPLEMENTACION
Contenido: Datos enteros, reales, o incluso, de Estructuras:
Estudiante, Auto, etc....
Y además, el nodo también contiene un enlace con su nodo
siguiente
Este enlace, puede no enlazar el nodo con nadie, el nodo esta solito,
no forma parte de ninguna lista
O puede “apuntar” a otro nodo, indicando que ese es su siguiente, formando
una Lista
19. TDA LSE_NODO:
DECLARACION
Un nodo dinámico almacena dentro
Un contenido de cualquier tipo de dato, entero, real,
estructuras, etc......
Un enlace, que es la referencia al nodo siguiente, la dirección
del nodo siguiente
typedef struct TLSE_Nodo{
Generico G;
struct TLSE_Nodo *sig;
} LSE_Nodo;
typedef LSE_Nodo * LSE_NodoPtr;
20. LSE_NODO: CREAR Y
DESTRUIR
Al crear un nodo se le
asignara un valor inicial
Al eliminar un nodo se
liberara la memoria que este
ocupa
LSE_nodo *LSE_CrearNodo(Generico G)
{
LSE_nodo *p;
p = (LSE_nodo *)malloc(sizeof(LSE_nodo));
if(p)
{
p->G = G;
p->sig = NULL;
}
return p;
}
void LSE_EliminarNodo(LSE_nodo *p)
{
if(p)
{
free(p);
p = NULL;
}
}
21. LSE: NIVEL DE
IMPLEMENTACION
En una lista hay que llevar control de las posiciones al
primer y el último elemento
En la lista, las posiciones son direcciones de memoria:
punteros
Header y Last son punteros a Nodo en una lista enlazada
La posición de un nodo estará dada por un puntero a
dicho nodo
Una lista enlazada no tiene datos predefinidos
Los elementos o Nodos van siendo creados y eliminados a
medida que se va necesitando
23. LSE: DECLARACIÓN
Hay varias formas de definir una lista
Solo a través del primer elemento a la misma
O llevando control del primero y el último elemento
typedef struct{
LSE_Nodo *header;
LSE_Nodo *last;
}LSE;
typedef LSE_Nodo * LSE;
24. LSE: CREAR y DESTRUIR
Al crear una lista, esta
estará vacía, su primero y
ultimo no apuntaran a
nadie
Al Eliminar una lista, cada
uno de los nodos debe ser
liberado
void LSE_Vaciar(LSE *L){
LSE_nodo *p;
while(!LSE_EstaVacia(*L)){
p = LSE_SacarNodoFin(L);
LSE_Nodo_Eliminar(p);
}
}
LSE *LSE_CrearLista(){
LSE *lnueva;
lnueva = malloc(sizeof(LSE));
lnueva->header = lnueva->last =
NULL;
return lnueva;
}
header
last
25. LSE: BUSQUEDA
Hay que ubicarse en el inicio: header
E ir avanzando hasta
Encontrar el nodo con la información
buscada o
Que ya no haya mas nodos
Como no se usan índices, se usan
punteros:
Un puntero se ubicara en el header
Y luego irá avanzando al siguiente, y al
siguiente y al siguiente
Al encontrar al nodo buscado, no se
retorna su posición como índice, esta
no importa
Se retorna la dirección de este
nodo(puntero)
LSE_nodo *LSE_BuscarNodo(LSE L,
Generico G, Generico_fnComparar f){
LSE_nodo *p;
for(p = L.header; p!= NULL; p = LSE_Nodo_Siguiente(p)){
if(f(LSE_Nodo_Contenido(p),G) ==0) return(p);
}
return(NULL);
}
10 5 8 2 31
25
last
header
Busco 25
p p p p
p
Busco 30
p p p p p
p
Recomendación:
Usemos el
comportamiento
de LSE_Nodo, no el
estado
26. LSE: ANTERIOR
Dada la dirección de un nodo(pos), esta función retorna la dirección del
nodo anterior
Hay que “buscar” desde el header
El nodo buscado es aquel cuyo siguiente es igual a pos
Si el elemento buscado es el primero en la lista, no hay anterior
LSE_nodo * LSE_Anterior(LSE L, LSE_nodo *p){
LSE_nodo *q;
if(LSE_EstaVacia(L)) return NULL;
if(L.header == p) return NULL;
for(q=L.header; q!=NULL;q=q->sig){
if(q->sig ==p) return(q);
}
return(NULL);
} 10 5 8 2 31
25
last
header
p
q q q
7
p
La
posición
p es la
de un
nodo
fuera de
la lista
q q q q q q
q
Ejemplo al
usar el
estado de
LSE_Nodo
27. LSE: PRIMERO Y ULTIMO
Se pueden obtener siempre y cuando
la lista no este vacía
Retornaran la información del
elemento apuntado por header y last
respectivamente.
LSE_nodo *LSE_NodoInicio(LSE L){
return (L.header);
}
LSE_nodo *LSE_NodoFin(LSE L){
return (L.last);
}
28. LSE: INSERTAR
La operación de insertar recibirá
La NODO en si que se va a insertar
Este nodo ya debió haber sido creado antes de
insertarlo
Hay varios tipos de inserción
Insertar al inicio o al final
Insertar en medio de la lista
29. INSERCION AL INICIO/FINAL
bool LSE_InsertarNodoInicio(LSE *L,
LSE_nodo *nuevo){
if (!nuevo) return FALSE;
if(LSE_EstaVacia(*L)){
L->header = L->last = nuevo;
} else{
LSE_Nodo_ModificarEnlace(
nuevo, L->header);
L->header = nuevo;
}
return(TRUE);
}
bool LSE_InsertarNodoFin(LSE *L, LSE_nodo
*nuevo){
if(!nuevo) return FALSE;
if(LSE_EstaVacia(*L)){
L->header = L->last = nuevo;
} else{
LSE_Nodo_ModificarEnlace(L->last,,
nuevo);
L->last = nuevo;
}
return(TRUE);
}
10 5 8 2 31
25
last
header
9
nuevo
header
nuevo->sig =
header;
header =
nuevo;
18
nuevo
last->sig =
nuevo;
last
last = nuevo;
Si la lista esta vacía, tanto header como last apuntan al
nuevo nodo
Si no, si es al inicio el nuevo header, es el nuevo nodo
Si no, si es al final, el nuevo last es el nuevo nodo
30. INSERTAR
EN MEDIO
Debe recibir la posición donde se va
a insertar
Insertemos después
Si Lista Vacía, el nuevo header y last,
es el nuevo nodo
Si la posición no existe no efectuar
la inserción
Si la lista solo tiene un elemento, el
nuevo last es el nuevo nodo
bool LSE_Insertar(LSE *L,
LSE_nodo *p,
LSE_nodo *nuevo){
if(L->last==p){//Insertar al final
return(LSE_InsertarNodoFin(L, nuevo));
}else{
if(!p || ! LSE_ExisteNodo(*L,p))
return FALSE;
if(LSE_EstaVacia(*L))
L->header = L->last = nuevo;
else{
LSE_Nodo_ModificarEnlace(
nuevo, LSE_Nodo_Siguiente(p));
LSE_Nodo_ModificarEnlace(p, nuevo);
}
}
return(TRUE);
}
10 5 8 2 31
25
header last
p
18
nuevo
Nuevo->sig = p->sig;
p->sig = nuevo;
header
last
18
nuevo
header
last
header = last = nuevo:
31. LSE: SACAR DE LA LISTA
La lista no debe estar vacía!!, si tiene un solo elemento, dejarla
vacía
LSE_nodo *LSE_SacarNodoInicio(LSE *L){
LSE_nodo *tmp = L->header;
if(LSE_EstaVacia(*L)) return NULL;
if(L->header == L->last)
LSE_InicializarLista(L);
else {
L->header = L->header->sig;
}
return(tmp);
}
LSE_nodo *LSE_SacarNodoFin(LSE *L){
LSE_nodo *tmp=L->header;
if(LSE_EstaVacia(*L)) return NULL;
if(L->header == L->last)
LSE_InicializarLista(L);
else{
tmp = L->last;
L->last = LSE_Anterior(*L, L->last);
L->last->sig = NULL;
}
return(tmp);
}
10 5 8 2 31
25
header last
tmp
Tmp = header; header
Header = header->sig;
free(tmp);
tmp
tmp = last;
last
Last = Anterior(Last);
free(tmp);
Last->sig = NULL;
32. LSE: SACAR JUSTO UN
NODO
Lista no debe estar vacía
La posición enviada debe existir
en la lista
Revisar si se desea eliminar el
header o el last
bool LSE_EliminarxPos(LSE *L, LSE_nodo *p){
LSE_nodo *p_ant;
if(LSE_EstaVacia(*L)) return 0;
if(!p || !LSE_ExisteNodo(*L, p))
return FALSE;
if(p==L->header)
LSE_SacarNodoInicio(L);
else if(p == L->last)
LSE_SacarNodoFin(L);
else{
p_ant = LSE_Anterior(*L,p);
p_ant->sig = p->sig;
p->sig = NULL;
}
return(TRUE);
}
10 5 8 2 31
25
header last
p
pant
p_ant = Anterior(p);
p_ant->sig = p->sig;
free(p);
33. VISUALIZAR
Imprimir todos los nodos de la lista
void LSE_Recorrer(LSE L, Generico_fnImprimir fn){
LSE_nodo *p;
for( p = L.header; p!=NULL; p = p->sig)
fn(p->G);
}