Matrices (Arreglos Bidimensionales)¶
En el ámbito de la programación, una matriz se define como una estructura de datos que facilita el almacenamiento de un conjunto homogéneo de elementos, organizados en una disposición bidimensional de filas y columnas. En el lenguaje de programación C, esta abstracción se materializa mediante la implementación de arreglos bidimensionales (2D), los cuales pueden ser conceptualizados como arreglos cuyos elementos son, a su vez, otros arreglos.
Las matrices son fundamentales en numerosas aplicaciones: desde operaciones matemáticas básicas hasta algoritmos complejos de procesamiento de imágenes, simulaciones físicas, análisis de datos, representación de grafos, implementación de juegos como el tres en raya o ajedrez, y sistemas de coordenadas bidimensionales. Su comprensión es esencial para el desarrollo de software eficiente y estructurado.
Relación con el álgebra lineal¶
Las matrices en programación están íntimamente relacionadas con el concepto matemático de matriz del álgebra lineal. Esto permite aplicar directamente teoremas y algoritmos matemáticos en implementaciones de software, especialmente en campos como gráficos por computadora, machine learning, y simulaciones científicas.
Extensión a múltiples dimensiones¶
Técnicamente, no están limitadas a dos dimensiones. Podés tener arreglos
tridimensionales (int cubo[3][4][5]
) o de mayor dimensionalidad. Sin embargo,
las aplicaciones prácticas se vuelven menos claras y la complejidad de manejo
aumenta considerablemente. Todos los conceptos presentados aquí se extienden
naturalmente a estas dimensiones superiores.
Declaración¶
La declaración de una matriz requiere la especificación del tipo de dato de sus elementos, un identificador único y las dimensiones correspondientes al número de filas y columnas.
Sintaxis
tipo_dato nombre_matriz[CANTIDAD_FILAS][CANTIDAD_COLUMNAS];
Ejemplo
int miMatriz[3][4]; // Matriz de 3 filas y 4 columnas
Figure 1:Representación de una matriz bidimensional en memoria. Los elementos se almacenan de forma contigua siguiendo el orden row-major, donde cada fila se almacena completa antes de pasar a la siguiente.
Inicialización¶
Podemos inicializar nuestras matrices, esencialmente, de dos formas diferentes, con un inicializador como con los arreglos, o con código.
Figure 2:Tres métodos de inicialización de matrices: con inicializador completo, con declaración implícita de la primera dimensión, y programáticamente mediante lazos.
Inicialización completa¶
Este proceso se realiza mediante el uso de llaves anidadas, donde cada conjunto de llaves interno corresponde a una fila de la matriz.
1 2 3 4
int matriz[2][3] = { {1, 2, 3}, // Fila 0 {4, 5, 6} // Fila 1 };
Inicialización con declaración implícita¶
En C, es posible omitir la primera dimensión (filas) durante la inicialización, pero todas las dimensiones subsecuentes deben ser especificadas explícitamente. Esto se debe a que el compilador necesita conocer el tamaño de cada “sub-arreglo” para calcular las posiciones de memoria.
1 2 3 4 5
// Válido: el compilador infiere 2 filas basándose en el inicializador. int matriz[][3] = { {1, 2, 3}, // Fila 0 {4, 5, 6} // Fila 1 };
La forma int matriz[][]
es inválida y no compilará, ya que el compilador
no tendría forma de saber dónde termina una fila y empieza la siguiente.
Inicialización manual¶
Constituye un método más flexible y programático. El uso de macros en mayúsculas
para las dimensiones (Regla 0x002Fh
: Las constantes (const
o #define
) deben nombrarse en MAYUSCULAS_SNAKE_CASE
) y de size_t
para los índices
(Regla 0x002Eh
: Las variables que representan tamaños o índices de arreglos deben ser de tipo size_t
) son buenas prácticas que mejoran la legibilidad y portabilidad.
#define FILAS 3
#define COLUMNAS 4
int matriz[FILAS][COLUMNAS];
for (size_t i = 0; i < FILAS; i++) {
for (size_t j = 0; j < COLUMNAS; j++) {
matriz[i][j] = i * 10 + j;
}
}
Asignación de valores mediante lazo anidados
Acceso a los Elementos¶
El acceso a un elemento específico de la matriz se realiza mediante la especificación de sus índices de fila y columna, los cuales son de base cero.
Sintaxis
nombre_matriz[indice_fila][indice_columna];
Ejemplo de L-Value y R-Value
matriz[0][1] = 100; // Asigna 100 al elemento en la fila 0, columna 1.
int valor = matriz[2][3]; // Toma el valor del elemento en la fila 2, columna 3.
Patrones de Recorrido de Matrices¶
El procesamiento sistemático de todos los elementos de una matriz requiere el uso de lazos anidados. La comprensión de los diferentes patrones de acceso es crucial tanto para la corrección del algoritmo como para el rendimiento del programa.
Recorrido por Filas (Row-Major)¶
El patrón más común y eficiente es el recorrido por filas, donde se accede a
todos los elementos de una fila antes de pasar a la siguiente. Este patrón
aprovecha la localidad espacial y optimiza el uso de la
memoria caché (Regla 0x0000h
: La claridad y prolijidad son de máxima importancia).
1 2 3 4 5 6 7 8
// Lazo externo: filas (i) for (size_t i = 0; i < FILAS; i++) { // Lazo interno: columnas (j) for (size_t j = 0; j < COLUMNAS; j++) { printf("%d ", matriz[i][j]); } printf("\n"); // Salto de línea al final de cada fila }
Recorrido fila por fila - patrón recomendado
Recorrido por Columnas (Column-Major)¶
En ocasiones específicas, puede ser necesario procesar los elementos columna por columna. Este patrón es menos eficiente en términos de caché, pero puede ser requerido por la lógica del algoritmo.
// Lazo externo: columnas (j)
for (size_t j = 0; j < COLUMNAS; j++) {
// Lazo interno: filas (i)
for (size_t i = 0; i < FILAS; i++) {
printf("%d ", matriz[i][j]);
}
printf("\n"); // Nueva línea al final de cada columna
}
Recorrido columna por columna
Figure 3:Comparación entre el recorrido por filas (row-major) y por columnas (column-major). El recorrido por filas accede a elementos contiguos en memoria, aprovechando la caché. El recorrido por columnas genera saltos en memoria, causando más fallos de caché.
Recorrido Diagonal¶
Para matrices cuadradas, es común necesitar acceder a las diagonales.
#define DIM 4
int matriz_cuadrada[DIM][DIM];
// Diagonal principal (i == j)
printf("Diagonal principal: ");
for (size_t i = 0; i < DIM; i++) {
printf("%d ", matriz_cuadrada[i][i]);
}
printf("\n");
// Diagonal secundaria (i + j == DIM - 1)
printf("Diagonal secundaria: ");
for (size_t i = 0; i < DIM; i++) {
printf("%d ", matriz_cuadrada[i][DIM - 1 - i]);
}
printf("\n");
Acceso a diagonal principal y secundaria
Figure 4:Las diagonales principal y secundaria en una matriz cuadrada. La diagonal
principal cumple la condición i == j
, mientras que la secundaria cumple
i + j == DIM - 1
.
Pasando matrices a funciones (Método Clásico)¶
Al pasar una matriz como argumento a una función, el estándar de C requiere que se especifiquen explícitamente todas las dimensiones, a excepción de la primera. Esto es necesario para que el compilador pueda calcular el desplazamiento en memoria de cada elemento.
1 2 3 4 5 6 7 8 9 10 11
#define COLUMNAS 4 // Es crucial pasar las dimensiones para cumplir con la regla {ref}`0x0027h`. void imprimir_matriz(int mat[][COLUMNAS], size_t filas, size_t columnas) { for (size_t i = 0; i < filas; i++) { for (size_t j = 0; j < columnas; j++) { printf("%d\t", mat[i][j]); } printf("\n"); } }
¿Acaso las columnas
no están ya en el macro COLUMNAS
? Para garantizar la
consistencia y minimizar los efectos secundarios en las funciones que operan con
matrices, es fundamental ser explícito con el contexto en el que deben trabajar.
En este caso, las columnas como argumento evita que nuestro código dependa de un valor que es esencialmente externo a la misma y aunque funciona perfectamente sin él, en el momento en que veamos memoria dinámica, el código no funcionará sin mayores cambios.
Cálculo de Desplazamiento de Memoria¶
Dicha información es indispensable para que el compilador pueda calcular
correctamente el desplazamiento de memoria necesario para localizar cualquier
elemento matriz[i][j]
, utilizando una fórmula análoga a:
Pasando matrices a funciones (Método ALV)¶
La utilización de ALV’s facilita la creación de funciones genéricas capaces de operar sobre matrices de dimensiones arbitrarias.
// Correcto: filas y cols se conocen antes de que el compilador procese matriz[filas][cols]
void procesar_matriz(size_t filas, size_t cols, int matriz[filas][cols]) {
printf("\nProcesando matriz de %zu x %zu\n", filas, cols);
for (size_t i = 0; i < filas; i++) {
for (size_t j = 0; j < cols; j++) {
matriz[i][j] *= 2; // Ejemplo: duplicar cada valor
}
}
}
Matrices Multidimensionales¶
El lenguaje C no impone un límite de dos dimensiones para los arreglos; es posible declarar arreglos multidimensionales. Un arreglo tridimensional, por ejemplo, puede conceptualizarse como un cubo de datos.
Figure 5:Arreglo tridimensional representado como capas de matrices bidimensionales. Cada capa contiene una matriz completa, y el acceso requiere tres índices: capa, fila y columna.
// Arreglo tridimensional: 2 capas, 3 filas, y 4 columnas.
int cubo[2][3][4];
// Acceso a un elemento
cubo[1][0][2] = 99;
// Recorrido con tres lazos anidados
for (size_t i = 0; i < 2; i++) { // Capas
for (size_t j = 0; j < 3; j++) { // Filas
for (size_t k = 0; k < 4; k++) { // Columnas
cubo[i][j][k] = i + j + k;
}
}
}
Declaración y recorrido de un arreglo 3D
Consideraciones de Rendimiento: Localidad de Caché¶
El método empleado para iterar sobre los elementos de una matriz posee un impacto significativo sobre el rendimiento computacional. Este fenómeno se atribuye a la arquitectura de memoria jerárquica de los procesadores modernos, los cuales utilizan una memoria caché de alta velocidad como intermediaria entre la CPU y la memoria principal (RAM).
Figure 6:Impacto del orden de acceso en el rendimiento. El acceso row-major aprovecha la localidad espacial, cargando elementos contiguos en la caché. El acceso column-major genera saltos que causan múltiples fallos de caché.
Recorrido Óptimo (Cache-Friendly)¶
La iteración por filas en el lazo externo y por columnas en el interno produce un patrón de acceso secuencial a la memoria. Esto maximiza la probabilidad de aciertos de caché (cache hits), ya que al solicitar un elemento, un bloque contiguo de memoria que incluye los elementos subsiguientes de la misma fila es transferido a la caché.
1 2 3 4 5 6
// ALTO RENDIMIENTO: Aprovecha la localidad de datos espaciales. for (size_t i = 0; i < FILAS; i++) { for (size_t j = 0; j < COLUMNAS; j++) { suma += matriz[i][j]; } }
Recorrido Ineficiente (Cache-Unfriendly)¶
La inversión de los lazos de iteración genera un patrón de acceso no contiguo a la memoria. Cada acceso salta a una dirección de memoria distante, lo que reduce la eficacia de la caché y provoca constantes fallos de caché (cache misses). Cada fallo obliga a la CPU a esperar la recuperación de datos desde la lenta memoria principal, degradando sustancialmente el rendimiento.
1 2 3 4 5 6
// BAJO RENDIMIENTO: Genera fallos de caché frecuentes. for (size_t j = 0; j < COLUMNAS; j++) { for (size_t i = 0; i < FILAS; i++) { suma += matriz[i][j]; } }
Operaciones Matemáticas con Matrices¶
En el ámbito de la programación en y otras áreas de la computación, el manejo de matrices es fundamental. continuación, te presento los algoritmos y las expresiones matemáticas para las operaciones básicas entre matrices.
Figure 7:Operaciones básicas con matrices: suma, resta y transposición. Cada operación requiere validar que las dimensiones sean compatibles antes de proceder.
Suma de Matrices¶
La suma de dos matrices, y , de las mismas dimensiones (m x n), da como resultado una matriz de la misma dimensión. Cada elemento de es la suma de los elementos correspondientes en y .
Expresión Matemática¶
Para dos matrices y de tamaño , la matriz resultante se define como:
donde representa la fila y la columna.
Expansión Matemática¶
Visualmente, la suma de dos matrices de 2x2 se vería así:
Algoritmo en Pseudocódigo¶
El algoritmo recorre ambas matrices y suma los elementos en la misma posición.
1 2 3 4 5 6 7 8 9 10 11 12
FUNCIÓN sumar_matrices(A, B, m, n) // A y B son matrices de dimensión m x n CREAR matriz C de tamaño m x n PARA i DESDE 0 HASTA m - 1 PARA j DESDE 0 HASTA n - 1 C[i][j] = A[i][j] + B[i][j] FIN PARA FIN PARA RETORNAR C FIN FUNCIÓN
Algoritmo para la suma de dos matrices y .
Resta de Matrices¶
De manera análoga a la suma, la resta de dos matrices y de idénticas dimensiones resulta en una matriz donde cada elemento es la diferencia de los elementos correspondientes.
Expresión Matemática¶
Para dos matrices y de tamaño , la matriz resultante se define como:
Algoritmo en Pseudocódigo¶
El procedimiento es idéntico al de la suma, pero se realiza una resta.
1 2 3 4 5 6 7 8 9 10 11 12
FUNCIÓN restar_matrices(A, B, m, n) // A y B son matrices de dimensión m x n CREAR matriz C de tamaño m x n PARA i DESDE 0 HASTA m - 1 PARA j DESDE 0 HASTA n - 1 C[i][j] = A[i][j] - B[i][j] FIN PARA FIN PARA RETORNAR C FIN FUNCIÓN
Algoritmo para la resta de dos matrices y .
Multiplicación de Matrices¶
La multiplicación de una matriz de dimensión por una matriz de dimensión produce una matriz de dimensión . Es crucial que el número de columnas de sea igual al número de filas de .
Figure 8:Proceso de multiplicación de matrices. Cada elemento C[i][j] se calcula como el producto escalar de la fila i de A con la columna j de B, sumando los productos elemento por elemento.
Expresión Matemática¶
El elemento de la matriz resultante se calcula como la suma de los productos de los elementos de la fila 𝑖 de por los elementos de la columna 𝑗 de .
Expansión Matemática¶
Cada elemento de la matriz resultante se calcula realizando el producto escalar del vector fila de la matriz con el vector columna 𝑗 de la matriz .
Dadas las matrices:
El elemento se calcula como:
Por ejemplo, para calcular el elemento de una multiplicación de matrices de 2x2:
Donde .
Algoritmo en Pseudocódigo¶
Este algoritmo requiere tres lazos anidados para calcular el producto escalar de cada fila de con cada columna de .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
FUNCIÓN multiplicar_matrices(A, B, m, p, n) // A es una matriz de m x p // B es una matriz de p x n CREAR matriz C de tamaño m x n PARA i DESDE 0 HASTA m - 1 PARA j DESDE 0 HASTA n - 1 suma = 0 PARA k DESDE 0 HASTA p - 1 suma = suma + (A[i][k] * B[k][j]) FIN PARA C[i][j] = suma FIN PARA FIN PARA RETORNAR C FIN FUNCIÓN
Algoritmo para la multiplicación de una matriz A (m x p) por una matriz B (p x n).
Cálculo de Determinantes¶
El determinante es un valor escalar que se puede calcular para toda matriz cuadrada. Este valor encapsula propiedades importantes de la matriz, como la invertibilidad. Se denota como o .
Definición Matemática¶
Para una matriz de 2x2, el cálculo es directo:
Para matrices de mayor tamaño , un método común es la expansión por cofactores. El determinante se calcula expandiendo a lo largo de una fila o columna. Usando la primera fila, la fórmula es:
Donde:
es el elemento en la primera fila y la columna .
es la matriz menor, que es la submatriz que resulta de eliminar la fila 1 y la columna de .
El término se conoce como el cofactor del elemento .
Algoritmo Recursivo (Basado en Cofactores)¶
Este método matemático se traduce de forma natural en un algoritmo recursivo. La idea es reducir el problema de un determinante al cálculo de varios determinantes , hasta llegar al caso base de una matriz 2x2.
Costo Computacional
Este algoritmo es conceptualmente claro, pero computacionalmente ineficiente para matrices grandes, con una complejidad de . Para aplicaciones de alto rendimiento, se utilizan otros métodos como la descomposición LU.
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
FUNCIÓN calcular_determinante(A, n) // A es una matriz cuadrada de dimensión n x n SI n == 1 ENTONCES RETORNAR A[0][0] FIN SI SI n == 2 ENTONCES RETORNAR (A[0][0] * A[1][1]) - (A[0][1] * A[1][0]) FIN SI determinante_total = 0 PARA j_actual DESDE 0 HASTA n - 1 // 1. Crear la submatriz (menor) M CREAR submatriz M de tamaño (n-1) x (n-1) PARA i DESDE 1 HASTA n - 1 col_sub = 0 PARA j DESDE 0 HASTA n - 1 SI j != j_actual ENTONCES M[i-1][col_sub] = A[i][j] col_sub = col_sub + 1 FIN SI FIN PARA FIN PARA // 2. Calcular el signo del cofactor signo = (-1)^j_actual // o potencia( -1, j_actual) // 3. Suma recursiva sub_determinante = calcular_determinante(M, n-1) determinante_total = determinante_total + (signo * A[0][j_actual] * sub_determinante) FIN PARA RETORNAR determinante_total FIN FUNCIÓN
Algoritmo recursivo para el cálculo del determinante.
Inversión de Matrices¶
La inversa de una matriz cuadrada , denotada como , es aquella matriz que al multiplicarla por da como resultado la matriz identidad .
Condiciones para la Inversión¶
Una matriz es invertible si y solo si cumple dos condiciones:
Es una matriz cuadrada.
Su determinante es distinto de cero. las matrices con determinante cero se las llama singulares y no tienen inversa.
Método de la Matriz Adjunta¶
Un método para encontrar la inversa se basa en el determinante y la matriz adjunta. La fórmula es:
Donde es la matriz adjunta de , que se define como la transpuesta de la matriz de cofactores de .
Algoritmo (Basado en la Adjunta)¶
El algoritmo consiste en seguir los pasos de la fórmula matemática.
Calcular el determinante: Si es cero, la matriz no es invertible.
Calcular la matriz de cofactores: Para cada elemento , su cofactor es .
Calcular la matriz adjunta: Transponer la matriz de cofactores.
Obtener la inversa: Multiplicar la matriz adjunta por el escalar .
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
FUNCIÓN invertir_matriz(A, n) // 1. Calcular determinante determinante = calcular_determinante(A, n) SI determinante == 0 ENTONCES RETORNAR ERROR "La matriz es singular y no se puede invertir." FIN SI // 2. Calcular la matriz de cofactores CREAR matriz_cofactores de tamaño n x n PARA i DESDE 0 HASTA n - 1 PARA j DESDE 0 HASTA n - 1 // a. Crear la submatriz menor M(i,j) CREAR submatriz M de (n-1) x (n-1) omitiendo fila i y columna j de A // b. Calcular el signo y el determinante del menor signo = (-1)^(i+j) det_menor = calcular_determinante(M, n-1) matriz_cofactores[i][j] = signo * det_menor FIN PARA FIN PARA // 3. Calcular la matriz adjunta (transpuesta de la de cofactores) CREAR matriz_adjunta de tamaño n x n PARA i DESDE 0 HASTA n - 1 PARA j DESDE 0 HASTA n - 1 matriz_adjunta[j][i] = matriz_cofactores[i][j] FIN PARA FIN PARA // 4. Calcular la inversa dividiendo la adjunta por el determinante CREAR matriz_inversa de tamaño n x n factor_inversion = 1.0 / determinante PARA i DESDE 0 HASTA n - 1 PARA j DESDE 0 HASTA n - 1 matriz_inversa[i][j] = matriz_adjunta[i][j] * factor_inversion FIN PARA FIN PARA RETORNAR matriz_inversa FIN FUNCIÓN
Algoritmo para la inversión de una matriz A.
Validación y Manejo de Errores¶
En aplicaciones robustas, es fundamental implementar validaciones para prevenir
accesos fuera de límites y operaciones inválidas. Esto es especialmente crítico
en C, donde no existe verificación automática de límites (Regla 0x0027h
: Verificá siempre los límites de los arreglos antes de acceder a sus elementos).
Figure 9:Validación de dimensiones para operaciones con matrices. La suma y resta requieren dimensiones idénticas, mientras que la multiplicación requiere que las columnas de A sean igual a las filas de B.
Validación de Índices¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include <stdbool.h> bool indice_valido(int fila, int columna, size_t max_filas, size_t max_columnas) { return (fila >= 0 && fila < max_filas && columna >= 0 && columna < max_columnas); } int acceso_seguro_matriz(int matriz[][MAX_COLUMNAS], int fila, int columna, int filas, int columnas) { if (!indice_valido(fila, columna, filas, columnas)) { fprintf(stderr, "Error: Índices fuera de límites (%d, %d)\n", fila, columna); return -1; // Valor de error } return matriz[fila][columna]; }
Función para validar acceso seguro a matriz
Validación de Operaciones¶
Para operaciones matemáticas entre matrices, debemos verificar la compatibilidad de dimensiones antes de proceder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
typedef enum { MATRIZ_OK, MATRIZ_ERROR_DIMENSIONES, MATRIZ_ERROR_MEMORIA, MATRIZ_ERROR_SINGULAR } resultado_matriz_t; resultado_matriz_t validar_suma(int filas_a, int columnas_a, int filas_b, int columnas_b) { if (filas_a != filas_b || columnas_a != columnas_b) { return MATRIZ_ERROR_DIMENSIONES; } return MATRIZ_OK; } resultado_matriz_t validar_multiplicacion(int filas_a, int columnas_a, int filas_b, int columnas_b) { if (columnas_a != filas_b) { return MATRIZ_ERROR_DIMENSIONES; } return MATRIZ_OK; }
Validación para operaciones con matrices
Mejores Prácticas y Optimizaciones¶
Uso de Macros para Dimensiones¶
Utilizá siempre macros para definir las dimensiones de tus matrices, siguiendo
la regla de estilo Regla 0x002Fh
: Las constantes (const
o #define
) deben nombrarse en MAYUSCULAS_SNAKE_CASE
. Esto facilita el mantenimiento y la
modificación del código.
#define MAX_FILAS 100
#define MAX_COLUMNAS 100
int matriz[MAX_FILAS][MAX_COLUMNAS];
Definición de dimensiones con macros
Optimización de Acceso a Memoria¶
Para matrices grandes, considerá el orden de acceso para optimizar el uso de la caché. El patrón row-major es generalmente más eficiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Preferible - acceso secuencial por filas for (size_t i = 0; i < FILAS; i++) { for (size_t j = 0; j < COLUMNAS; j++) { procesar_elemento(matriz[i][j]); } } // Menos eficiente - acceso por columnas // Usar solo cuando sea necesario por la lógica del algoritmo for (size_t j = 0; j < COLUMNAS; j++) { for (size_t i = 0; i < FILAS; i++) { procesar_elemento(matriz[i][j]); } }
Optimización para el acceso a memoria
Funciones Auxiliares¶
Creá funciones auxiliares para operaciones comunes, siguiendo la regla de
claridad Regla 0x0000h
: La claridad y prolijidad son de máxima importancia:
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
void imprimir_matriz(int matriz[][MAX_COLS], size_t filas, size_t columnas) { for (size_t i = 0; i < filas; i++) { for (size_t j = 0; j < columnas; j++) { printf("%4d ", matriz[i][j]); } printf("\n"); } } void inicializar_con_ceros(int matriz[][MAX_COLS], size_t filas, size_t columnas) { for (size_t i = 0; i < filas; i++) { for (size_t j = 0; j < columnas; j++) { matriz[i][j] = 0; } } } bool son_matrices_iguales(int a[][MAX_COLS], int b[][MAX_COLS], int filas, int columnas) { for (size_t i = 0; i < filas; i++) { for (size_t j = 0; j < columnas; j++) { if (a[i][j] != b[i][j]) { return false; } } } return true; }
Funciones auxiliares para matrices
Glosario¶
- memoria caché
Una memoria caché (del francés cacher, “esconder”) es un componente de hardware o software que almacena datos para que las futuras solicitudes de esos datos puedan ser atendidas más rápidamente. Se trata de una memoria auxiliar, de alta velocidad y menor capacidad, situada entre la unidad central de procesamiento (CPU) y la memoria de acceso aleatorio (RAM).
El objetivo principal de una caché es acelerar el acceso a los datos que se utilizan con mayor frecuencia. Cuando la CPU necesita leer o escribir datos, primero busca en la caché. Si los datos se encuentran allí (lo que se conoce como un acierto de caché o cache hit), se accede a ellos de forma casi inmediata, evitando el acceso mucho más lento a la memoria principal. Si los datos no están en la caché (fallo de caché o cache miss), se deben recuperar de la RAM y, por lo general, se copian en la caché para futuros accesos.
Existen diferentes niveles de caché (L1, L2, L3), que se diferencian por su tamaño, velocidad y proximidad a los núcleos de la CPU. La caché L1 es la más pequeña y rápida, mientras que la L3 es la más grande y lenta de las tres.
¿Pero por que no todo es memoria caché? La relación costo capacidad. Las memorias mas cercanas al procesador y las mas rápidas, son las mas caras, tengan en cuenta que una computadora moderna tiene algunos kilobytes de memoria L1 y unos pocos megabytes en L3.
- localidad espacial
Un principio fundamental en el diseño de sistemas de memoria que establece que si un programa accede a una ubicación de memoria, es muy probable que también acceda a ubicaciones cercanas en un futuro próximo. Este principio es especialmente relevante para las matrices almacenadas en row-major order, donde los elementos de una fila son adyacentes en memoria. Al acceder secuencialmente por filas, se aprovecha esta localidad y se optimiza el uso de la caché.
- localidad temporal
Principio que indica que si un programa accede a una ubicación de memoria, es probable que vuelva a acceder a la misma ubicación en un futuro cercano. En el contexto de matrices, esto se aprovecha cuando se realizan múltiples operaciones sobre los mismos elementos o cuando se recorren matrices varias veces con diferentes propósitos.
- row-major order
Método de almacenamiento de matrices en memoria donde los elementos se disponen fila por fila de forma consecutiva. En una matriz de 3×4, los elementos se almacenan como: [matriz[0][0], matriz[0][1], matriz[0][2], matriz[0][3], matriz[1][0], ...]. Este es el orden usado por C, C++, Python (NumPy) y Java, entre otros.
- column-major order
Método alternativo de almacenamiento donde los elementos se almacenan columna por columna. Usado por lenguajes como Fortran y MATLAB. En una matriz de 3×4, el orden sería: [matriz[0][0], matriz[1][0], matriz[2][0], matriz[0][1], ...]. Es importante conocer este concepto al interoperar con código de otros lenguajes.
- matriz singular
Una matriz cuadrada cuyo determinante es igual a cero. Las matrices singulares no tienen inversa y representan transformaciones que “colapsan” el espacio, reduciendo su dimensionalidad. En términos geométricos, una matriz singular proyecta vectores de dimensión n en un subespacio de menor dimensión.
- determinante
Un valor escalar que se puede calcular para cualquier matriz cuadrada. Proporciona información importante sobre las propiedades de la matriz: si es cero, la matriz es singular; si es positivo o negativo, indica orientación; y su magnitud representa el factor de escalamiento del volumen en transformaciones lineales.
- matriz identidad
Una matriz cuadrada especial donde todos los elementos de la diagonal principal son 1 y todos los demás elementos son 0. Actúa como el elemento neutro en la multiplicación de matrices: A × I = I × A = A. Es fundamental en operaciones como la inversión de matrices.
Ejercicios¶
Solution to Exercise matrices-1
#include <stdio.h>
#define FILAS 3
#define COLUMNAS 3
int main() {
int mi_matriz[FILAS][COLUMNAS] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
printf("Contenido de la matriz:\n");
for (size_t i = 0; i < FILAS; i++) {
for (size_t j = 0; j < COLUMNAS; j++) {
printf("%d\t", mi_matriz[i][j]);
}
printf("\n"); // Salto de línea al final de cada fila
}
return 0;
}
Solution to Exercise funciones-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#include <stdio.h> #define DIM 3 // La función recibe la matriz y su dimensión int sumar_diagonal_principal(int matriz[][DIM], int dimension) { int suma = 0; for (int i = 0; i < dimension; i++) { suma = suma + matriz[i][i]; // Accedemos solo a los elementos donde fila == columna } return suma; } int main() { int matriz_cuadrada[DIM][DIM] = {{10, 2, 3}, {4, 20, 6}, {7, 8, 30}}; int suma = sumar_diagonal_principal(matriz_cuadrada, DIM); printf("La suma de la diagonal principal es: %d\n", suma); return 0; }