qsort con structs en C: ejercicio resuelto

qsort con structs en C: ejercicio resuelto

Si buscas qsort con structs en C ejercicio resuelto, aquí tienes la forma de pasar un comparador personalizado a qsort para ordenar un array de structs por distintos campos. qsort de <stdlib.h> acepta punteros a void en el comparador, por lo que hay que hacer un cast explícito al tipo concreto.

La firma del comparador debe ser int cmp(const void *a, const void *b) y devolver negativo, cero o positivo según el orden relativo de a y b.

Enunciado

Define una struct Alumno con campos nombre (array de 32 caracteres), nota (double) y edad (int). Crea un array de 5 alumnos y ordénalo de tres formas:

  1. Por nota descendente (mayor nota primero).
  2. Por nombre ascendente (orden alfabético).
  3. Por edad ascendente y, en caso de empate, por nota descendente.

Solución en C

 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_N 32

typedef struct {
    char   nombre[MAX_N];
    double nota;
    int    edad;
} Alumno;

/* ── Comparadores ──────────────────────────────────────────────── */

/* Por nota descendente */
int cmp_nota_desc(const void *a, const void *b) {
    const Alumno *x = (const Alumno *)a;
    const Alumno *y = (const Alumno *)b;
    if (y->nota > x->nota) return  1;
    if (y->nota < x->nota) return -1;
    return 0;
}

/* Por nombre ascendente */
int cmp_nombre_asc(const void *a, const void *b) {
    const Alumno *x = (const Alumno *)a;
    const Alumno *y = (const Alumno *)b;
    return strncmp(x->nombre, y->nombre, MAX_N);
}

/* Por edad ascendente, empate → nota descendente */
int cmp_edad_nota(const void *a, const void *b) {
    const Alumno *x = (const Alumno *)a;
    const Alumno *y = (const Alumno *)b;
    if (x->edad != y->edad) return x->edad - y->edad;
    /* Empate en edad: nota descendente */
    if (y->nota > x->nota) return  1;
    if (y->nota < x->nota) return -1;
    return 0;
}

/* ── Utilidades ─────────────────────────────────────────────────── */

void imprimir(const char *titulo, const Alumno *arr, int n) {
    printf("%s:\n", titulo);
    for (int i = 0; i < n; i++)
        printf("  %-16s nota=%.1f  edad=%d\n",
               arr[i].nombre, arr[i].nota, arr[i].edad);
}

int main(void) {
    Alumno alumnos[] = {
        {"Laura",   8.5, 21},
        {"Pedro",   6.0, 22},
        {"Sofia",   9.2, 21},
        {"Carlos",  7.8, 20},
        {"Marta",   9.2, 22},
    };
    int n = sizeof(alumnos) / sizeof(alumnos[0]);

    qsort(alumnos, n, sizeof(Alumno), cmp_nota_desc);
    imprimir("Por nota (desc)", alumnos, n);

    qsort(alumnos, n, sizeof(Alumno), cmp_nombre_asc);
    imprimir("\nPor nombre (asc)", alumnos, n);

    qsort(alumnos, n, sizeof(Alumno), cmp_edad_nota);
    imprimir("\nPor edad (asc) + nota (desc)", alumnos, n);

    return 0;
}

Resultado esperado

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Por nota (desc):
  Sofia            nota=9.2  edad=21
  Marta            nota=9.2  edad=22
  Laura            nota=8.5  edad=21
  Carlos           nota=7.8  edad=20
  Pedro            nota=6.0  edad=22

Por nombre (asc):
  Carlos           nota=7.8  edad=20
  Laura            nota=8.5  edad=21
  Marta            nota=9.2  edad=22
  Pedro            nota=6.0  edad=22
  Sofia            nota=9.2  edad=21

Por edad (asc) + nota (desc):
  Carlos           nota=7.8  edad=20
  Sofia            nota=9.2  edad=21
  Laura            nota=8.5  edad=21
  Marta            nota=9.2  edad=22
  Pedro            nota=6.0  edad=22

Errores frecuentes

  • Comparar doubles con a->nota - b->nota y devolver el resultado directamente: la diferencia puede ser un double muy pequeño que se trunca a 0 en el cast a int, dando empates incorrectos. Hay que usar comparaciones explícitas (>, <).
  • No usar const Alumno * en el comparador: el cast desde const void * a un puntero no-const es válido pero genera advertencias del compilador; usar const es lo correcto.
  • Olvidar el tercer argumento de qsort (sizeof(Alumno)): si se pasa sizeof(Alumno *) (tamaño de puntero), qsort interpreta los datos de forma incorrecta.
  • No usar strncmp con el límite MAX_N para comparar cadenas en struct: strcmp puede leer más allá del campo si no está terminado en \0.

Aplicación práctica

qsort con comparadores personalizados es la herramienta estándar de C para ordenar cualquier array de registros: tablas de una base de datos en memoria, resultados de consultas, clasificaciones de usuarios, ordenación de archivos por nombre/tamaño/fecha y cualquier escenario donde el criterio de orden depende de múltiples campos.

Siguiente ejercicio recomendado

Práctica guiada y libro completo

Si quieres una ruta completa con progresión real de dificultad:

FAQ

¿qsort es estable? ¿Mantiene el orden de elementos iguales?

No está garantizado que sea estable. La implementación estándar en la mayoría de plataformas usa introsort (combinación de quicksort + heapsort), que no es estable. Si necesitas estabilidad, puedes añadir el índice original al comparador como criterio de desempate final.

¿Qué diferencia hay entre qsort y bsearch?

qsort ordena un array en su sitio; bsearch busca un elemento en un array ya ordenado, ambas en O(n log n) y O(log n) respectivamente. Se usan juntas: primero qsort, luego bsearch.

¿Se puede usar qsort con un array de punteros a struct?

Sí, y es frecuente cuando los elementos son grandes. El comparador recibe const void *a que es const Alumno **, por lo que el cast es *(const Alumno **)a. Ordenar punteros en lugar de structs completas evita copiar los datos durante la ordenación.