×

Langue

Fermer
Atelier 801
  • Forums
  • Dev Tracker
  • Connexion
    • English Français
      Português do Brasil Español
      Türkçe Polski
      Magyar Română
      العربية Skandinavisk
      Nederlands Deutsch
      Bahasa Indonesia Русский
      中文 Filipino
      Lietuvių kalba 日本語
      Suomi עברית
      Italiano Česky
      Hrvatski Slovensky
      Български Latviešu
      Estonian
  • Langue
  • Forums
  • /
  • Atelier 801
  • /
  • Hors-sujet
  • /
  • Ciencia y actualidad
  • /
  • Cómo crear una lista simplemente enlazada en C
Cómo crear una lista simplemente enlazada en C
Obemice
« Sénateur »
1543174800000
    • Obemice#0095
    • Profil
    • Derniers messages
    • Tribu
#1
  8
  • Introducción
  • Listas dinámicas
  • Ejercicios

Cómo crear una lista simplemente enlazada en C, sin morirte en el intento

https://i.imgur.com/YBhrEM0.png



Cosas que vas a necesitar:

  • Un IDE que compile C.
  • Tener una leve idea de algún lenguaje de programación.
  • Teclado
  • 200gr de azúcar
  • Leche parcialmente descremada
  • Leer esto



Lo básico básico (básico al cuadrado)



En ciencias de la computación, el concepto de una lista no varía mucho del concepto que tenemos los seres humanos de una lista en la vida real. Una lista común y corriente (como la que usamos para ir de compras) es una serie de "cosas" en la cual a cada "cosa" le sigue otra. Esta misma lista puede respetar un cierto orden (Leche -> Manzanas -> Papas), o estar completamente desordenada (Manzanas -> Papas -> Leche).

Este mismo concepto es el que se intenta (en realidad, se intentó y se logró, y ya está mucho más que establecido) trasladar a todo el ámbito de la computación. ¿Por qué? Porque las listas son algo que utilizamos muy a menudo para resolver problemas en la vida real, y si pudiésemos llevar esa herramienta a una computadora, y que la computadora se encargue de resolver esos problemas en vez de nosotros, estaríamos ahorrándonos trabajo (...que es en realidad la historia de la computación, resumida en una oración).


Lo no tan básico (raíz de básico al cuadrado)



La mayoría de los programadores, incluidos los principiantes, trabajaron con listas. Probablemente no lo supiesen, pero las listas están ahí.

Y es que, el tipo más básico de lista, es un array, un vector de elementos. Recordemos que un vector no es nada más que un conjunto de espacios de memoria contiguos. Es decir, son pedacitos de memoria cada uno al lado del otro. Esto hace sonar una alarma: los vectores, sospechosamente, se parecen a una lista.

Porque lo son. Los vectores son listas estáticas.

1
2
3
4
5
//Declaramos el vector...
int vector[10], i;

for (i = 0; i < 10; i++)
vector[i] = i;

==> Momento que soy lento. ¿Cómo que listas estáticas? ¿Qué es eso?

En programación hay dos grandes formas de manejar la memoria. Para un programador principiante, la forma más conocida que tendrá es la de manejarla estáticamente.

En el ejemplo de arriba se ve cómo declaramos un vector de enteros de 10 posiciones. Lo que estamos haciendo, en realidad, es (decirle al compilador que reserve) reservar 10 bloques de memoria contiguos del tamaño de un entero. Esta memoria tiene la particularidad de que va a permanecer reservada durante toda la ejecución del programa. La vamos a poder usar como queramos mientras nuestro programa se ejecute.

Esto es genial. Quiere decir que el problema ya está solucionado: ya se trasladaron las listas de la vida real a la computación. ¿Qué más se podría hacer?

A pesar de que los vectores son hermosos y solucionan el problema de las listas, traen una gran desventaja: necesitamos conocer la cantidad de elementos que queremos que existan dentro del vector al momento de reservarlo en memoria. A simple vista no parece un desafío, pero, ¿y si no conocemos la cantidad de elementos? ¿O si la cantidad es variable?

Una solución a esto sería reservar un vector de una gran cantidad de elementos, y rezar porque nunca lleguen más elementos de los que tenemos disponibles.

Lo cual es horrible y más ineficiente que calentar café dejándolo en la luz del sol por 3 años. Estaríamos reservando muchísima memoria que quizás el sistema necesite para otras cosas, y además nos estaríamos arriesgando a quedarnos cortos con la cantidad.

Acá es donde entra en juego la memoria dinámica.

Lo no tan básico menos uno



Una de las diferencias que tiene la memoria dinámica con la estática es que podemos reservarla cuando nosotros realmente queramos y la necesitemos. Si necesitamos solamente ingresar 1 elemento, reservaremos memoria para ese elemento y nadie más. Con esto, solo vamos a estar reservando memoria cuando realmente tengamos que usarla, y no reservaremos 500 espacios de memoria que quizás se usen alguna vez.

Otra diferencia, es que es posible que la memoria dinámica no necesariamente se reserve en espacios contiguos. Podemos reservar memoria para dos elementos y que el primero quede en China, y el otro en Jujuy...

Con la ayuda de la memoria dinámica es donde entran en juego las listas dinámicas. Estas listas tienen la gran ventaja de que su cantidad de elementos es variable, pudiendo tener tantos elementos como memoria tengamos, y pudiendo liberar la memoria de aquellos elementos que ya no necesitemos.

Entonces... ¿cómo hacemos una de estas?

(¡volteá la pestaña!) (¿se puede hacer eso?) (bueno ni idea, vos solo andá a la otra)

Lo no tan básico, tendiendo al infinito negativo



Para poder hacer una lista, primero tenemos que definir cómo va a estar formada la misma. Sabemos que necesitan ser capaces de albergar algún tipo de contenido, pero además de eso, necesitamos saber cuál es el elemento que le sigue al actual.

Debido a que no estamos trabajando en memoria dinámica, no podemos asumir que todos los elementos de la lista estén pegaditos uno al lado del otro en la memoria. Por eso sí o sí, necesitamos que cada elemento de nuestra lista, además de tener un contenido, tenga un algo que apunte hacia el elemento que le sigue.

En programación, este tipo de estructura recibe el nombre de nodo. El nodo no es más que una estructura que se divide en dos: la primera parte es el dato, el contenido que guardamos. La segunda parte es lo que "apunta al siguiente" nodo.


https://i.imgur.com/be71aH6.png
Fig. 1: un nodo



Pasar esto a C no sería tan complicado, ¿no? Necesitaríamos declarar una estructura que sea algo más o menos así:

1
2
3
4
5
typedef struct
{
t_dato dato; //t_dato es el tipo de dato que vamos a guardar
t_nodo * sig; //t_nodo * sig es un puntero que apunta al siguiente nodo de la lista.
} t_nodo;

Esto no sirve. Más que nada porque estamos cometiendo el error de "llamar" al tipo de dato t_nodo antes de que siquiera lo terminemos de definir.

Una forma de solucionar esto es poniéndole un nombre a la estructura para poder hacer referencia a la misma.

1
2
3
4
5
typedef struct s_nodo
{
t_dato dato; //t_dato es el tipo de dato que vamos a guardar
struct s_nodo * sig; //* sig es un puntero que apunta al siguiente nodo de la lista.
} t_nodo;

Tenemos los nodos declarados y su estructura definida. Una parte fundamental de la lista está hecha. Pero necesitamos tener la lista en sí. Es decir: necesitamos crear un tipo de dato lista, con el cual podamos declarar y utilizar listas.

Definimos el tipo de dato t_lista como un puntero a nodo.

1
typedef t_nodo * t_lista;

https://i.imgur.com/mwlzHmC.png

Fig. 2: lo mismo de arriba, pero en colorcitos lindos



Lo no tan básico, ahora en el conjunto de los complejos



Un par de cuestiones a tener en cuenta:

  • En nuestro caso, vamos a manejar las listas trabajando con un puntero a lista. Ya vamos a ver más adelante por qué.
  • En las listas simplemente enlazadas, el puntero de posición siempre apunta al primer elemento de la lista.


Veamos el código que aparece en la imagen del principio de todo:

1
2
3
4
5
6
int main()
{
t_lista lista;
crearLista(&lista); /** ¡No puedo creer que me compile!*/
return 0;
}

Analizándolo, podemos ver que reservamos una variable estática del tipo de dato t_lista, y que inmediatamente después llamamos a una función crearLista(). El porqué de esto reside en la siguiente cuestión: cuando reservamos memoria (de cualquier tipo), no podemos asegurarnos de que el bloque que hayamos reservado esté vacío. Lo más probable es que ese bloque tenga basura dentro. Por eso lo primero que tenemos que hacer es remover esa basura seteando al contenido de lista en NULL.

1
2
3
4
void crearLista(t_lista * pl) //Recibimos la dirección de memoria de lista, ergo, un puntero a lista
{
*pl = NULL; //Seteamos al contenido en nulo
}

Tenemos nuestra lista creada y sin ningún elemento. Podemos empezar a llenarla.

Si queremos ingresar un elemento al principio de la lista, lo podemos hacer de la siguiente forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Inserta elementos siempre al principio de la lista
int insertarPrincipio(t_lista *pl, const t_dato *d) //Recibe un puntero a lista y un puntero a dato
{
t_nodo * nuevo = (t_nodo *) malloc(sizeof(t_nodo)); //Reservamos la cantidad suficiente de memoria para guardar un nodo

if (!nuevo) //Si no pudimos reservar memoria, abortamos (libre y gratuitamente)
return SIN_MEM;

//Asignamos el dato al nuevo nodo
nuevo->dato = *d;
//Al siguiente le asignamos el nodo al que apunta la lista actualmente (el anterior "primero")
nuevo->sig = *pl;
//Cambiamos el primer elemento de la lista por este nodo
*pl = nuevo;
//Retornamos que se ejecutó todo bien
return SUCCESS;
}

De esta forma, pudimos agregar un elemento a la lista. Y también, todos los elementos que sean agregados con esta función se pondrán automáticamente al comienzo de la lista.


Draw some circles, draw the rest of the fucking owl



¿Pero y qué pasa si queremos insertar nuestros datos de forma ordenada?

Esto implica que necesitaremos recorrer la lista e ir comparando el dato que queremos ingresar con los datos ya presentes en la lista.

https://i.imgur.com/c0tZtTy.png
Fig. 3: simple esquema ilustrativo



Para esto nos será conveniente utilizar una función parametrizada, y además de eso, abusar de que en C, los parámetros solo pueden pasarse por copia. Por esto mismo podemos empezar a recorrer la lista cambiando simplemente el contenido de pl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int insertarOrdenado(t_lista * pl, const t_dato *d, t_cmp comparar)
{
int cmp;

t_nodo * nuevo = (t_nodo *) malloc(sizeof(t_nodo));

if (!nuevo)
return SIN_MEM;

while (*pl && (cmp = comparar(&(*pl)->dato, &d)) < 0)
pl = &(*pl)->sig;

nuevo->dato = *d;
nuevo->sig = *pl;
*pl = nuevo;

return SUCCESS;
}
Habiendo aprendido el concepto de listas simplementes enlazadas en C, sírvase de resolver estos ejercicios para perfeccionar sus conocimientos.


Ejercicio 1:

El banco “CBCI”, mantiene el estado de las cuentas de sus clientes en el archivo cuentas.dat (ordenado por el nro. de cuenta), que actualiza todos los días con los movimientos provenientes del archivo de texto movimientos.txt (ordenado por fecha y hora del movimiento) de la siguiente manera:

• Si el movimiento es un débito (D): se le debe restar al saldo de la cuenta cliente el importe del movimiento.
• Si el movimiento es un crédito (C): se debe sumar el importe del movimiento al saldo de la cuenta cliente.

El archivo de texto de movimientos tiene registros de longitud fija, con 8 caracteres para el código de cuenta, un carácter para el tipo de movimiento y 9 caracteres (con 2 decimales) para el importe del movimiento.

Se pide: Realizar la actualización del archivo de cuentas con la información de los movimientos. Debe minimizar el acceso a disco, debe valerse de una lista simplemente enlazada para ello.

Resuelva la función insertar_lista que inserta los elementos de manera ordenada y en caso de que esté duplicado, los acumula.

Resuelva la función sacar_primero_lista que saca el primero de la lista.


Ejercicio 2:

Una materia de una Universidad mantiene un archivo de alumnos inscriptos a la materia (alumnos.bin), que contiene el dni y el apellido y nombre del alumno, y otro archivo con las notas de cada alumno (notas.txt), que contiene el dni del alumno, el tipo de examen (P1, P2, R1, R2) y la nota que se sacó el alumno en el examen, los ausentes no se encuentran en este archivo. El archivo de alumnos está ordenado por dni, y el archivo de notas está ordenado por dni del alumno y tipo de examen.

Al finalizar el cuatrimestre se realiza el proceso de generación de acta, del cual sale el archivo acta.txt con los datos de los alumnos y todas sus notas parciales, la nota final (que debe ser calculada según el criterio
que se detalla a continuación) y la condición final (Pro, Apr, Rec, Aus, Err). Debe quedar ordenada por apellido y nombre del alumno.

Cálculo de nota final:
Las notas se dividen en 4 grupos:
- Ausente
- las de reprobación (1 a 3),
- las de cursada (4 a 6)
- las de promoción (7 a 10)

Las notas de los recuperatorios pisan la nota parcial (salvo que estén ausentes). Entonces, si las notas parciales están en el mismo grupo, la nota final es el promedio de las mismas (7 y 8 = 8). Si están en distinto grupo, queda la nota del grupo inferior (4 y 9 = 4). Si la nota de alguno de los 2 parciales queda en ausente (teniendo en cuenta los recuperatorios) la nota final es ausente (A y 7 = A).

Los errores deben ser informados en un archivo de texto (observaciones.txt) y los que se pueden dar son:
- que un alumno tenga notas en los 2 recuperatorios. En este caso la nota debe quedar como Err y también debe ser informada en un archivo de texto,
- que una nota en el archivo notas.txt NO tenga su correspondiente alumno en alumnos.bin,
- también deben ser informadas en el archivo de observaciones, aunque no sea un error, si algún alumno recuperó un parcial en el que tenía 7 o más.
Zayde7
1543178880000
    • Zayde7#0000
    • Profil
    • Derniers messages
#2
[Modéré par Bog, raison : Si no tienen nada que aportar no comenten, por favor.]
Sebastianes
1543377720000
    • Sebastianes#0000
    • Profil
    • Derniers messages
    • Tribu
#3
[Modéré par Bog, raison : Si no tienen nada que aportar no comenten, por favor.]
Karim_uwu03
« Censeur »
1546492560000
    • Karim_uwu03#0510
    • Profil
    • Derniers messages
#4
  0
Disculpa, no entendimos tu hilo.
Iovis
« Citoyen »
1571614140000
    • Iovis#9042
    • Profil
    • Derniers messages
#5
  0
No entendí muy bien, ¿es esto un array con una cantidad de elementos dinámicos o es un array que permite el guardado de distintos tipos de datos?

Ya que el primero se puede realizar de esta manera:
1
2
3
x=(int*)calloc(n, sizeof(int));
/* ... */
x=realloc(x, m);

En caso de que sea lo segundo, pues sería bastante útil, pero con la leída que le dí al hilo parece que no se trata de eso.

¿Cuál sería la diferencia entre esto y un array con dos o más dimensiones? ¿Cuál es la utilidad de esto en programación general?
Intas
« Citoyen »
1571894460000
    • Intas#0123
    • Profil
    • Derniers messages
#6
  1
Para entender esto tenés que tener en claro lo que son las estructuras y punteros a las mismas, cosa que el hilo fue hecho asumiendo que eso ya se sabe.

Una estructura es, básicamente, una agrupación de datos que pueden (o no) tener distintos tipos de variables. Esa es la principal diferencia entre un array y una estructura: la primera puede tener las dimensiones que quieras pero solo pueden tener un tipo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Esto es un array de 10 elementos del tipo int declarado de forma estática
int vector[10];

// Esto también es un array de 10 elementos declarado de forma dinámica,
// pero sus elementos siguen siendo solo del tipo int
int *vecter = (int*)calloc(10, sizeof(int));

// Defino una estructura con 2 elementos del tipo int y float, y le llamo "Vector"
typedef struct {
int vector[10];
float otroVector[10];
} Vector;

// "Vector" es simplemente como le llamé a la estructura y me sirve
// para declarar las variables que yo quiera que tengan esos elementos

// Declaro una variable tipo Vector, que es la estructura que puse arriba
Vector miVector;

// Declaro un puntero a la estructura y le asigno memoria
Vector *miVectorptr;
miVectorptr = (Vector*)malloc(sizeof(Vector));

// Guaramos algunos valores
for (int i=0; i < 10; i++){
// Se usa el punto para acceder a los elementos de la estructura
miVector.vector[i] = i;
// Como la variable es un puntero, si queremos acceder a cualquier
// elemento usando el punto los paréntesis son obligatorios
(*miVectorptr).vector[i] = i+1;
}

for (int i=0; i < 10; i++){
// Como es una re paja escribir los paréntesis y el asterisco siempre,
// el lenguaje C nos provee una sintaxis especial para manejar punteros a estructuras.
// En vez del punto se usa una flecha, así que escribir esto:
//printf("En el puntero: %d\n", (*miVectorptr).vector[i]);
// es equivalente a esto:
printf("En el puntero: %d\n", miVectorptr->vector[i]);
printf("En el que no es puntero: %d\n", miVector.vector[i]);
}


Obviamente hay mucho más sobre estructuras que no cubrí arriba. espero no haberme mandado una porque escribí eso de memoria y no sé si compila lol

a dit :
¿Cuál sería la diferencia entre esto y un array con dos o más dimensiones?

Un array está limitado a un solo tipo de variable, una lista enlazada podés tener casi cualquier cosa.

a dit :
¿Cuál es la utilidad de esto en programación general?

No sé cómo sea en otros lenguajes, pero en C esto es muy útil por estas razones:

op a dit :

Una de las diferencias que tiene la memoria dinámica con la estática es que podemos reservarla cuando nosotros realmente queramos y la necesitemos. Si necesitamos solamente ingresar 1 elemento, reservaremos memoria para ese elemento y nadie más. Con esto, solo vamos a estar reservando memoria cuando realmente tengamos que usarla, y no reservaremos 500 espacios de memoria que quizás se usen alguna vez.

Otra diferencia, es que es posible que la memoria dinámica no necesariamente se reserve en espacios contiguos.

Con la ayuda de la memoria dinámica es donde entran en juego las listas dinámicas. Estas listas tienen la gran ventaja de que su cantidad de elementos es variable, pudiendo tener tantos elementos como memoria tengamos, y pudiendo liberar la memoria de aquellos elementos que ya no necesitemos.

Simplemente es una forma más eficiente de guardar y/u ordenar datos.
Iovis
« Citoyen »
1571966160000
    • Iovis#9042
    • Profil
    • Derniers messages
#7
  0
Muchas gracias por la respuesta Intas.

Voy a profundizar en el tema y si logro algo editaré este mensaje e incluiré el código explicado a detalle, como manera de complementar al hilo. Espero que eso pueda ser de utilidad para otras personas.
  • Forums
  • /
  • Atelier 801
  • /
  • Hors-sujet
  • /
  • Ciencia y actualidad
  • /
  • Cómo crear una lista simplemente enlazada en C
© Atelier801 2018

Equipe Conditions Générales d'Utilisation Politique de Confidentialité Contact

Version 1.27