Saltar al contenido
ES EN
Tema: automático

Ejercicio resuelto: matrices dinámicas con malloc y free en C

Actualizado el

Código de Examen C Estructuras de datos Nivel examen Código probado

Ejercicio de examen resuelto en C: reservar una matriz dinámica, rotarla 90 grados, eliminar filas vacías liberándolas con free y no perder memoria.

Código de Examen: Ejercicio resuelto: matrices dinámicas con malloc y free en C
Programación II · Examen parcialEjercicio 2 · 2,5 puntos · 30 min

Un almacén automatizado guarda en una matriz de F filas y C columnas las unidades que hay en cada hueco de sus estanterías. El monitor del operario está montado en vertical, así que el plano debe mostrarse rotado 90 grados en sentido horario. Además, el operario no quiere ver los pasillos vacíos: tras la rotación hay que quitar las filas formadas únicamente por ceros.

Las dimensiones solo se conocen en tiempo de ejecución, así que es obligatorio usar memoria dinámica: un vector de punteros reservado con malloc y una reserva independiente para cada fila. No se permiten matrices estáticas ni VLA.

Escribe un programa en C que implemente, como mínimo, estas funciones: int **crear_matriz(int filas, int cols), que devuelve NULL si alguna reserva falla y en ese caso no deja memoria perdida; void liberar_matriz(int **m, int filas); int **rotar_derecha(int **m, int f, int c), que devuelve una matriz nueva de C filas y F columnas; e int compactar(int **m, int filas, int cols), que libera las filas que solo contienen ceros, desplaza hacia arriba los punteros de las demás conservando su orden y devuelve el nuevo número de filas.

Entrada (por la entrada estándar): dos enteros F y C con 0 ≤ F, C ≤ 100, seguidos de F·C enteros entre -1000 y 1000, leídos por filas. Una fila con valores que se anulan (por ejemplo, un 1 y un -1) no está vacía: solo cuenta como vacía si todos sus valores son 0.

Salida: si F o C valen 0, se imprime únicamente la línea VACIA. En otro caso se imprime la línea ROTADA C x F seguida de las C filas de la matriz rotada, y después la línea COMPACTADA K x F, donde K es el número de filas que quedan, seguida de esas K filas. En cada fila los valores van separados por un único espacio, sin espacio final. Si K es 0, tras la cabecera no se imprime nada más.

Al terminar, toda la memoria reservada debe haberse liberado exactamente una vez.

Entrada de ejemplo

3 4
5 0 2 0
1 0 0 0
7 0 4 0

Salida esperada

ROTADA 4 x 3
7 1 5
0 0 0
4 0 2
0 0 0
COMPACTADA 2 x 3
7 1 5
4 0 2
Reserva de la matriz con malloc (vector de punteros más una fila por puntero), comprobando cada reserva y liberando lo ya reservado si una falla
0,75
Rotación de 90 grados en sentido horario sobre una matriz nueva de C×F con los índices correctos
0,75
Compactación: liberar con free las filas formadas solo por ceros y desplazar los punteros sin copiar datos
0,5
Liberación completa y sin dobles free de todas las matrices al terminar
0,5

Pistas

Pista 1 · ¿Dónde va a parar cada elemento al rotar?

Dibuja una matriz de 2×3 y rótala a mano. Fíjate en que la primera columna original, leída de abajo arriba, se convierte en la primera fila de la rotada. Intenta expresar la posición destino del elemento m[i][j] en función de i, j y f.

Pista 2 · ¿Qué hago si falla la tercera reserva de fila?

Si el malloc de la fila i devuelve NULL, las filas 0..i-1 y el vector de punteros ya existen. Necesitas liberarlas antes de devolver NULL. Una función liberar_matriz que reciba el número de filas a liberar te sirve también para este caso: basta con pasarle i.

Pista 3 · ¿Hace falta copiar números para quitar filas?

No. Cada fila es un bloque independiente y la matriz solo guarda punteros a esos bloques. Para eliminar una fila basta con liberar su bloque y mover punteros en el vector, con dos índices: uno que recorre todas las filas y otro que marca la siguiente posición libre. Piensa luego cuántas filas tiene que liberar liberar_matriz al final.

Solución

Solución explicada

La solución separa las responsabilidades en funciones pequeñas: una crea la matriz y gestiona los fallos parciales, otra la libera, otra produce la versión rotada y otra elimina filas vacías. Así cada malloc tiene un free fácil de localizar, que es lo primero que un corrector busca en este tipo de ejercicio.

La matriz original se libera en cuanto se ha construido la rotada, porque ya no se necesita. Al final solo queda por liberar la matriz rotada y compactada, con su número real de filas.

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

/* Libera las 'filas' primeras filas y el vector de punteros. */
static void liberar_matriz(int **m, int filas)
{
    if (m == NULL) {
        return;
    }
    for (int i = 0; i < filas; i++) {
        free(m[i]);
    }
    free(m);
}

/* Reserva una matriz filas x cols. Si algo falla, no deja memoria perdida. */
static int **crear_matriz(int filas, int cols)
{
    int **m = malloc((size_t)filas * sizeof *m);
    if (m == NULL) {
        return NULL;
    }
    for (int i = 0; i < filas; i++) {
        m[i] = malloc((size_t)cols * sizeof *m[i]);
        if (m[i] == NULL) {
            liberar_matriz(m, i); /* solo las filas 0..i-1 existen */
            return NULL;
        }
    }
    return m;
}

/* Devuelve una matriz nueva de c filas y f columnas: m rotada 90 grados horario. */
static int **rotar_derecha(int **m, int f, int c)
{
    int **r = crear_matriz(c, f);
    if (r == NULL) {
        return NULL;
    }
    for (int i = 0; i < f; i++) {
        for (int j = 0; j < c; j++) {
            r[j][f - 1 - i] = m[i][j];
        }
    }
    return r;
}

static int fila_vacia(const int *fila, int n)
{
    for (int k = 0; k < n; k++) {
        if (fila[k] != 0) {
            return 0;
        }
    }
    return 1;
}

/* Libera las filas de ceros y sube los punteros restantes. Devuelve las filas que quedan. */
static int compactar(int **m, int filas, int cols)
{
    int k = 0;
    for (int i = 0; i < filas; i++) {
        if (fila_vacia(m[i], cols)) {
            free(m[i]);
        } else {
            m[k] = m[i];
            k++;
        }
    }
    return k;
}

static void imprimir_matriz(int **m, int filas, int cols)
{
    for (int i = 0; i < filas; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%s%d", j > 0 ? " " : "", m[i][j]);
        }
        putchar('\n');
    }
}

int main(void)
{
    int f, c;
    if (scanf("%d %d", &f, &c) != 2 || f < 0 || c < 0) {
        printf("ERROR\n");
        return EXIT_FAILURE;
    }
    if (f == 0 || c == 0) {
        printf("VACIA\n");
        return EXIT_SUCCESS;
    }

    int **m = crear_matriz(f, c);
    if (m == NULL) {
        fprintf(stderr, "Sin memoria\n");
        return EXIT_FAILURE;
    }
    for (int i = 0; i < f; i++) {
        for (int j = 0; j < c; j++) {
            if (scanf("%d", &m[i][j]) != 1) {
                liberar_matriz(m, f);
                printf("ERROR\n");
                return EXIT_FAILURE;
            }
        }
    }

    int **r = rotar_derecha(m, f, c);
    liberar_matriz(m, f); /* la original ya no hace falta */
    if (r == NULL) {
        fprintf(stderr, "Sin memoria\n");
        return EXIT_FAILURE;
    }

    printf("ROTADA %d x %d\n", c, f);
    imprimir_matriz(r, c, f);

    int k = compactar(r, c, f);
    printf("COMPACTADA %d x %d\n", k, f);
    imprimir_matriz(r, k, f);

    liberar_matriz(r, k); /* las filas vacias ya se liberaron en compactar */
    return EXIT_SUCCESS;
}

Reserva. crear_matriz hace primero un malloc para el vector de filas punteros y después uno por fila, de cols enteros. La expresión sizeof *m evita repetir el tipo: si mañana la matriz es de double, la reserva sigue siendo correcta. El detalle importante es el fallo parcial. Si falla la fila i, se llama a liberar_matriz(m, i), que libera exactamente las i filas ya creadas y el vector. Liberar filas filas sería un error, porque se haría free sobre punteros sin inicializar.

Rotación. En rotar_derecha el elemento m[i][j] pasa a r[j][f - 1 - i]. La columna j original se convierte en la fila j de la rotada. Como el índice de columna destino es f - 1 - i, la última fila original (i = f - 1) queda a la izquierda y la primera a la derecha. En el caso de ejemplo, la columna 0 original contiene 5, 1 y 7 de arriba abajo, y por eso la primera fila rotada es 7 1 5. La matriz resultante tiene c filas y f columnas, y por eso se reserva con crear_matriz(c, f) y no al revés.

Compactación. compactar usa dos índices. i recorre todas las filas y k marca el siguiente hueco libre. Si fila_vacia devuelve 1, se libera ese bloque con free(m[i]). Si no, su puntero se sube a m[k]. No se copia ni un solo entero, solo direcciones: es la ventaja de tener filas independientes. El orden relativo se conserva porque k nunca supera a i. En el ejemplo desaparecen las filas 1 y 3 de la rotada y quedan 7 1 5 y 4 0 2.

El criterio es «todos ceros», no «suma cero». Lo comprueba el caso de valores que se anulan: las filas -1 1 y 1 -1 suman 0 pero se conservan, y solo se elimina 0 0. En el caso de todo ceros, en cambio, desaparecen las tres filas: K vale 0 y no se imprime nada tras la cabecera.

Liberación final. Tras compactar, el vector r sigue ocupando espacio para c punteros, pero solo los k primeros apuntan a bloques vivos. Los demás son copias de punteros ya movidos o ya liberados. Por eso se llama a liberar_matriz(r, k). Si se usara c, habría dobles free, que es comportamiento indefinido. La matriz original se libera justo después de rotarla, lo que reduce el pico de memoria. Si quieres repasar cómo se pasan punteros a funciones, tienes el ejercicio de paso por valor y por referencia en C.

Casos límite. Con F o C igual a 0 el programa responde VACIA sin reservar nada. Así se evita el malloc(0), cuyo resultado depende de la implementación. Con una sola fila de 1×4, la rotada es una columna de 4×1 y la compactación elimina el único 0. Con 1×1, el elemento queda igual.

Código y tests en GitLab

Traza paso a paso · generada ejecutando el código

Pasoijvalorfila_rcol_rCelda
100502(0,0)
201012(0,1)
302222(0,2)
403032(0,3)
510101(1,0)
611011(1,1)
712021(1,2)
813031(1,3)
920700(2,0)
1021010(2,1)
1122420(2,2)
1223030(2,3)

Casos de prueba

CasoEntradaSalida esperadaSalida realResultado
Ejemplo del enunciado: 3x4 con dos pasillos vacíos3 4 5 0 2 0 1 0 0 0 7 0 4 0ROTADA 4 x 3 7 1 5 0 0 0 4 0 2 0 0 0 COMPACTADA 2 x 3 7 1 5 4 0 2ROTADA 4 x 3 7 1 5 0 0 0 4 0 2 0 0 0 COMPACTADA 2 x 3 7 1 5 4 0 2OK
Un solo elemento1 1 9ROTADA 1 x 1 9 COMPACTADA 1 x 1 9ROTADA 1 x 1 9 COMPACTADA 1 x 1 9OK
Todo ceros: la compactación lo elimina todo2 3 0 0 0 0 0 0ROTADA 3 x 2 0 0 0 0 0 0 COMPACTADA 0 x 2ROTADA 3 x 2 0 0 0 0 0 0 COMPACTADA 0 x 2OK
Dimensión cero0 5VACIAVACIAOK
Una fila: la rotada es una columna1 4 3 0 8 1ROTADA 4 x 1 3 0 8 1 COMPACTADA 3 x 1 3 8 1ROTADA 4 x 1 3 0 8 1 COMPACTADA 3 x 1 3 8 1OK
Valores que se anulan no son fila vacía2 3 1 -1 0 -1 1 0ROTADA 3 x 2 -1 1 1 -1 0 0 COMPACTADA 2 x 2 -1 1 1 -1ROTADA 3 x 2 -1 1 1 -1 0 0 COMPACTADA 2 x 2 -1 1 1 -1OK

Salidas reales: código compilado con gcc 14.4.0 (C17) y ejecutado en un contenedor aislado el 7 de octubre de 2026.

Complejidad

Tiempo: leer la matriz es O(F·C), la rotación visita cada celda una vez, O(F·C), y la compactación revisa cada fila rotada como mucho entera, también O(F·C) en el peor caso. Imprimir tiene el mismo coste. En total, O(F·C), lineal en el número de celdas, que es lo mínimo posible porque hay que leerlas todas.

Memoria: durante la rotación conviven la original y la rotada, de modo que el pico es de 2·F·C enteros más F + C punteros, es decir, O(F·C). Liberar la original nada más rotar evita que ese pico se prolongue durante la impresión. La compactación no reserva nada: trabaja en el mismo vector de punteros y solo libera memoria.

Errores típicos

  • Reservar la rotada con crear_matriz(f, c) en vez de crear_matriz(c, f): funciona por casualidad con matrices cuadradas y escribe fuera de los bloques en las demás.
  • Usar r[j][i] = m[i][j]: eso es la traspuesta, no la rotación. Falta invertir el orden de las filas con f - 1 - i.
  • Liberar al final con liberar_matriz(r, c) después de compactar, lo que provoca dobles free sobre filas ya liberadas o duplicadas en el vector.
  • En un fallo parcial de malloc, devolver NULL sin liberar las filas ya reservadas, o liberar todas las filas, incluidas las que nunca se reservaron.
  • Considerar vacía una fila cuya suma es 0, lo que elimina filas como -1 1 que contienen mercancía.
  • Liberar solo el vector de punteros con free(m) y olvidar las filas, o liberar las filas después del vector, cuando sus direcciones ya no son accesibles.

Variantes

Reserva en un único bloque contiguo

El profesor puede pedir que los datos ocupen un único bloque, por ejemplo para mejorar la localidad de caché. Se hacen dos reservas: m = malloc(filas * sizeof *m) y m[0] = malloc(filas * cols * sizeof **m). Después, para cada i, se apunta m[i] = m[0] + i * cols. La liberación se reduce a free(m[0]); free(m);. La pega es que la compactación ya no puede liberar filas sueltas: solo se pueden reordenar los punteros, y la memoria de las filas eliminadas no se devuelve hasta el final.

Rotación antihoraria o de 180 grados

Para rotar en sentido antihorario la asignación es r[c - 1 - j][i] = m[i][j], también sobre una matriz de C×F. Para 180 grados la matriz resultante es de F×C y se asigna r[f - 1 - i][c - 1 - j] = m[i][j]. Comprueba siempre la fórmula con una esquina: el elemento m[0][0] debe ir a la esquina que esperas. Para practicar más operaciones con matrices puedes ver el ejercicio de matrices en C con sumas, traspuesta y traza.

Historial de cambios

  1. Reescrito con la plantilla de Código de Examen: enunciado de examen, pistas, solución explicada, casos de prueba y código compilado y ejecutado; versión en inglés enlazada.
  2. Publicación original.

Elaborado con apoyo de IA y revisado por la redacción · Revisado por Kernel

Kernel

· Dirección de tecnología · España

«Que la salida coincida no demuestra nada; lo que demuestra que entiendes la memoria es saber por qué al final se liberan k filas y no C.»