Operaciones con matrices en C

En este artículo voy a explicar cómo se realizan algunas de las operaciones mas básicas con matrices y su implementación en un programa C que te calculará automáticamente el determinante, la matriz traspuesta, la matriz adjunta y la matriz invertida (si existiera). Para el ejemplo voy a usar una matriz cuadrada de orden 3. Pero antes refresquemos la memoria recordando qué es cada cosa y poniendo un fragmento de código para ver representada la explicación en C.

Matriz inicial de orden 3

Tenemos una matriz de entrada, por ejemplo:

A=\begin{pmatrix}3 & 5 & 1 \\ 2 & -1 & 0 \\ -1 & 3 & 1\end{pmatrix}

La primera parte del programa comienza con un bucle que nos pide introducir los datos de nuestra matriz cuadrada de orden 3:

Matriz matriz = malloc(sizeof(struct MatrizStruct));

for (i = 0; i < 3; i++) {
	for (j = 0; j < 3; j++) {
		printf("Numero %d,%d: ", i + 1, j + 1);
		scanf("%d", &matriz->matrix_a[i][j]);
	}
}

./Matrices
Numero 1,1: 3
Numero 1,2: 5
Numero 1,3: 1
Numero 2,1: 2
Numero 2,2: -1
Numero 2,3: 0
Numero 3,1: -1
Numero 3,2: 3
Numero 3,3: 1

Los datos se van a ir asignando a las direcciones de memoria correspondientes de cada coordenada ij en un array bidimensional que previamente he declarado en un struct, pensado para almacenar toda la información sobre la matriz.

struct MatrizStruct {
	int matrix_a[3][3];    //Este es el Array inicial
	int matrix_aT[3][3];
	int matrix_aAdj[3][3];
	int matrix_aInv[3][3];
};

Antes de todo esto, en un header de C he declarado Matriz como un tipo de dato (typedef). De este modo podré utilizar y modificar toda la estructura de la que se compone cada matriz.

struct MatrizStruct;
typedef struct MatrizStruct* Matriz;

Una vez que ya tenemos la matriz de entrada ya podemos hacer operaciones con ella.

Determinante

El determinante de una matriz (cuadrada) A lo designaremos por |A| o bien por det A, aunque la primera forma es la mas utilizada. Se trata del número que se le asocia tras realizar el siguiente cálculo:

\begin{vmatrix} A \end{vmatrix} = \begin{vmatrix}a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{vmatrix}

\begin{vmatrix} A \end{vmatrix} = a_{11}\begin{vmatrix}a_{22} & a_{23} \\ a_{32} & a_{32} \end{vmatrix} - a_{12}\begin{vmatrix}a_{21} & a_{23} \\ a_{31} & a_{32} \end{vmatrix} + a_{13}\begin{vmatrix}a_{21} & a_{22} \\ a_{31} & a_{32} \end{vmatrix}

Cuyo desarrollo es idéntico al que nos proporciona la conocida Regla de Sarrus:

\begin{vmatrix} A \end{vmatrix} = a_{11}a_{22}a_{33} + a_{21}a_{32}a_{13} + a_{31}a_{23}a_{12} - a_{13}a_{22}a_{31} - a_{23}a_{32}a_{11} - a_{33}a_{21}a_{12}

Para obtener el determinante de la matriz de entrada he utilizado la siguiente función a la que es necesario pasarle por argumento la estructura Matriz. Esta calcula el determinante y devuelve un dato de tipo int.

int determinante(Matriz m) {
   return (m->matrix_a[0][0] * m->matrix_a[1][1] * m->matrix_a[2][2])
   + (m->matrix_a[2][0] * m->matrix_a[0][1] * m->matrix_a[1][2])
   + (m->matrix_a[0][2] * m->matrix_a[1][0] * m->matrix_a[2][1])
   - (m->matrix_a[2][0] * m->matrix_a[1][1] * m->matrix_a[0][2])
   - (m->matrix_a[0][0] * m->matrix_a[1][2] * m->matrix_a[2][1])
   - (m->matrix_a[0][1] * m->matrix_a[1][0] * m->matrix_a[2][2]);
}

En este ejemplo, el determinante de la matriz de entrada A es el siguiente:

|A|=\begin{vmatrix}3 & 5 & 1 \\ 2 & -1 & 0 \\ -1 & 3 & 1\end{vmatrix}=-8

Matriz traspuesta

La matriz traspuesta de A se designa A^{T} y es una matriz en la que los elementos que formaban una fila en A pasan a ser los elementos de una columna en A^{T}. Véase en la siguiente notación:

A=\begin{pmatrix}a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{pmatrix} \Rightarrow A^{T}=\begin{pmatrix}a_{11} & a_{21} & a_{31} \\ a_{12} & a_{22} & a_{32} \\ a_{13} & a_{23} & a_{33} \end{pmatrix}

Por ejemplo:

A=\begin{pmatrix}3 & 5 & 1 \\ 2 & -1 & 0 \\ -1 & 3 & 1\end{pmatrix}\Rightarrow A^{T}=\begin{pmatrix}3 & 2 & -1 \\ 5 & -1 & 3 \\ 1 & 0 & 1\end{pmatrix}

Para obtener la matriz traspuesta de la matriz de entrada he utilizado la siguiente función a la que también es necesario pasarle por argumento la estructura Matriz. Esta función no solo modifica el array del struct si no que también devuelve un dato de tipo Matriz. Esto nos puede venir muy bien para otro tipo de operaciones que no se tratan en este programa.

Matriz traspuesta(Matriz m) {
	m->matrix_aT[0][0] = m->matrix_a[0][0];
	m->matrix_aT[0][1] = m->matrix_a[1][0];
	m->matrix_aT[0][2] = m->matrix_a[2][0];
	m->matrix_aT[1][0] = m->matrix_a[0][1];
	m->matrix_aT[1][1] = m->matrix_a[1][1];
	m->matrix_aT[1][2] = m->matrix_a[2][1];
	m->matrix_aT[2][0] = m->matrix_a[0][2];
	m->matrix_aT[2][1] = m->matrix_a[1][2];
	m->matrix_aT[2][2] = m->matrix_a[2][2];

	return m;
}

Matriz adjunta

Se denomina matriz adjunta de A o Adj(A) a la matriz \begin{pmatrix}A^{j}_{i}\end{pmatrix} obtenida al sustituir los elementos (a^{i}_{j}) por sus adjuntos A^{j}_{i}, es decir a la matriz formada por los adjuntos de los elementos. Por ejemplo:

A=\begin{pmatrix}3 & 5 & 1 \\ 2 & -1 & 0 \\ -1 & 3 & 1\end{pmatrix}

El cálculo sería el siguiente:

A^{1}_{1}=\begin{vmatrix} -1 & 0 \\ 3 & 1 \end{vmatrix}=-1, \ A^{2}_{1}=-\begin{vmatrix} \ \ 2 & 0 \\ -1 & 1 \end{vmatrix}=-2, \ A^{3}_{1}=\begin{vmatrix} \ \ 2 & -1 \\ -1 & 3 \end{vmatrix}=5

A^{1}_{2}=\begin{vmatrix} 5 & 1 \\ 3 & 1 \end{vmatrix}=-2, \ A^{2}_{2}=-\begin{vmatrix} \ \ 3 & 1 \\ -1 & 1 \end{vmatrix}=4, \ A^{3}_{2}=\begin{vmatrix} \ \ 3 & 5 \\ -1 & \ \ 3 \end{vmatrix}=-14

A^{1}_{3}=\begin{vmatrix} 5 & 1 \\ \ \ -1 & 0 \end{vmatrix}=1, \ A^{2}_{3}=-\begin{vmatrix} 3 & 1 \\ 2 & 0 \end{vmatrix}=2, \ A^{3}_{3}=\begin{vmatrix} 3 & 5 \\ 2 & \ \ -1 \end{vmatrix}=-13

Así pues, la matriz adjunta de A es:

Adj(A)=\begin{pmatrix} A^{1}_{1} & A^{2}_{1} & A^{3}_{1} \\ A^{1}_{2} & A^{2}_{2} & A^{3}_{2} \\ A^{1}_{3} & A^{2}_{3} & A^{3}_{3} \end{pmatrix}=\begin{pmatrix}-1 & -2 & 5 \\ -2 & 4 & -14 \\ 1 & 2 & -13\end{pmatrix}

Al igual que con la función para obtener la matriz traspuesta, la matriz adjunta también se obtiene con una función a a que pasaremos como argumento la estructura Matriz, a continuación se calculará cada elemento del array bidimensional perteneciente al struct y se asignarán los valores para esta nueva matriz. Esta función también tiene un return de tipo Matriz.

Matriz adjunta(Matriz m) {
	m->matrix_aAdj[0][0] = m->matrix_a[1][1] * m->matrix_a[2][2] - m->matrix_a[2][1] * m->matrix_a[1][2];
	m->matrix_aAdj[1][0] = -(m->matrix_a[1][0] * m->matrix_a[2][2] - m->matrix_a[2][0] * m->matrix_a[1][2]);
	m->matrix_aAdj[2][0] = m->matrix_a[1][0] * m->matrix_a[2][1] - m->matrix_a[2][0] * m->matrix_a[1][1];
	m->matrix_aAdj[0][1] = -(m->matrix_a[0][1] * m->matrix_a[2][2] - m->matrix_a[2][1] * m->matrix_a[0][2]);
	m->matrix_aAdj[1][1] = m->matrix_a[0][0] * m->matrix_a[2][2] - m->matrix_a[2][0] * m->matrix_a[0][2];
	m->matrix_aAdj[2][1] = -(m->matrix_a[0][0] * m->matrix_a[2][1] - m->matrix_a[2][0] * m->matrix_a[0][1]);
	m->matrix_aAdj[0][2] = m->matrix_a[0][1] * m->matrix_a[1][2] - m->matrix_a[1][1] * m->matrix_a[0][2];
	m->matrix_aAdj[1][2] = -(m->matrix_a[0][0] * m->matrix_a[1][2] - m->matrix_a[1][0] * m->matrix_a[0][2]);
	m->matrix_aAdj[2][2] = m->matrix_a[0][0] * m->matrix_a[1][1] - m->matrix_a[1][0] * m->matrix_a[0][1];

	return m;
}

Matriz inversa

Solo tienen matriz inversa aquellas matrices que su determinante es distinto de cero, en cuyo caso su matriz inversa A^{-1} es igual a la traspuesta de la matriz adjunta dividida por el determinante de A. Esta sería la notación:

A^{-1}=\frac{1}{\begin{vmatrix} A \end{vmatrix}}\begin{pmatrix} A^{1}_{1} & A^{1}_{2} & ...... & A^{1}_{n} \\ A^{2}_{1} & A^{2}_{2} & ...... & A^{2}_{n} \\ ... & ... & ...... & ... \\ A^{n}_{1} & A^{n}_{2} & ...... & A^{n}_{n}\end{pmatrix}

Una notación mas reducida seria:

A^{-1}=\frac{1}{\begin{vmatrix} A \end{vmatrix}}(Adj(A))^T

Para calcular la matriz inversa también hay una función que recibe la estructura Matriz como argumento. Se asignan los valores correspondientes a la matriz traspuesta de la adjunta (Adj(A))^{T} y finalmente tiene un return de la estructura de tipo Matriz ya modificada. Solo se invocará a esta función en el caso de que el determinante sea distinto de 0.

Matriz inversa(Matriz m) {
	m->matrix_aInv[0][0] = m->matrix_aAdj[0][0];
	m->matrix_aInv[1][0] = m->matrix_aAdj[1][0];
	m->matrix_aInv[2][0] = m->matrix_aAdj[2][0];
	m->matrix_aInv[0][1] = m->matrix_aAdj[0][1];
	m->matrix_aInv[1][1] = m->matrix_aAdj[1][1];
	m->matrix_aInv[2][1] = m->matrix_aAdj[2][1];
	m->matrix_aInv[0][2] = m->matrix_aAdj[0][2];
	m->matrix_aInv[1][2] = m->matrix_aAdj[1][2];
	m->matrix_aInv[2][2] = m->matrix_aAdj[2][2];

	return m;
}

Pero ojo, Esta función solo asigna los valores de la matriz traspuesta de la adjunta. No se generan fracciones por que el programa las ejecutaría y en ocasiones daría como resultado números decimales muy largos y no quedaría presentable, así pues, he decidido que se almacenen como numerador de la fracción que mas adelante se imprimirá por pantalla añadiéndole su denominador, que será el determinante de la matriz inicial |A|.

printf("\nMatriz inversa:\n");
	/* Solo se calculara la inversa si el determinante es != 0 */
	if(determinante(m) != 0){
		inversa(m);

		/* Imprime la matriz inversa. */
		for (i = 0; i < 3; i++) {
			for (j = 0; j < 3; j++) {
				printf("%3d/%d", m->matrix_aInv[i][j], determinante(m));
			}
			printf("\n");
		}
	} else {
		printf("No tiene invertida por que su determinante es 0.\n");
	}

Programa completo

Si queréis probar este modesto programa en vuestra máquina aquí os dejo el contenido de los dos archivos que necesitáis para compilarlo. Se admiten correcciones y mejoras que seguro que tiene un montón.

Archivo matriz.h
/*
 * matriz.h v0.01
 * Copyleft - 2017  Javier Dominguez Gomez
 * Written by Javier Dominguez Gomez <jdg@member.fsf.org>
 * GnuPG Key: 6ECD1616
 * Madrid, Spain
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef MATRIZ_H_
#define MATRIZ_H_

struct MatrizStruct;
typedef struct MatrizStruct* Matriz;

void printDatos(Matriz m);
int determinante(Matriz m);
Matriz traspuesta(Matriz m);
Matriz adjunta(Matriz m);
Matriz inversa(Matriz m);

#endif /* MATRIZ_H_ */

 

Archivo matriz.c
/*
 * matriz.c v0.01
 * Copyleft - 2017  Javier Dominguez Gomez
 * Written by Javier Dominguez Gomez <jdg@member.fsf.org>
 * GnuPG Key: 6ECD1616
 * Madrid, Spain
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Compilation: gcc -O0 -g3 -Wall -c -MF"matriz.d" -MT"matriz.o" -o "matriz.o" "matriz.c"
 *              gcc  -o "matriz" ./matriz.o
 *
 * Usage:       ./matriz
 */

#include "matriz.h"

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

int i, j;

struct MatrizStruct {
	int matrix_a[3][3];
	int matrix_aT[3][3];
	int matrix_aAdj[3][3];
	int matrix_aInv[3][3];
};

int determinante(Matriz m) {
	return (m->matrix_a[0][0] * m->matrix_a[1][1] * m->matrix_a[2][2])
			+ (m->matrix_a[2][0] * m->matrix_a[0][1] * m->matrix_a[1][2])
			+ (m->matrix_a[0][2] * m->matrix_a[1][0] * m->matrix_a[2][1])
			- (m->matrix_a[2][0] * m->matrix_a[1][1] * m->matrix_a[0][2])
			- (m->matrix_a[0][0] * m->matrix_a[1][2] * m->matrix_a[2][1])
			- (m->matrix_a[0][1] * m->matrix_a[1][0] * m->matrix_a[2][2]);
}

Matriz traspuesta(Matriz m) {
	m->matrix_aT[0][0] = m->matrix_a[0][0];
	m->matrix_aT[0][1] = m->matrix_a[1][0];
	m->matrix_aT[0][2] = m->matrix_a[2][0];
	m->matrix_aT[1][0] = m->matrix_a[0][1];
	m->matrix_aT[1][1] = m->matrix_a[1][1];
	m->matrix_aT[1][2] = m->matrix_a[2][1];
	m->matrix_aT[2][0] = m->matrix_a[0][2];
	m->matrix_aT[2][1] = m->matrix_a[1][2];
	m->matrix_aT[2][2] = m->matrix_a[2][2];

	return m;
}

Matriz adjunta(Matriz m) {
	m->matrix_aAdj[0][0] = m->matrix_a[1][1] * m->matrix_a[2][2] - m->matrix_a[2][1] * m->matrix_a[1][2];
	m->matrix_aAdj[1][0] = -(m->matrix_a[1][0] * m->matrix_a[2][2] - m->matrix_a[2][0] * m->matrix_a[1][2]);
	m->matrix_aAdj[2][0] = m->matrix_a[1][0] * m->matrix_a[2][1] - m->matrix_a[2][0] * m->matrix_a[1][1];
	m->matrix_aAdj[0][1] = -(m->matrix_a[0][1] * m->matrix_a[2][2] - m->matrix_a[2][1] * m->matrix_a[0][2]);
	m->matrix_aAdj[1][1] = m->matrix_a[0][0] * m->matrix_a[2][2] - m->matrix_a[2][0] * m->matrix_a[0][2];
	m->matrix_aAdj[2][1] = -(m->matrix_a[0][0] * m->matrix_a[2][1] - m->matrix_a[2][0] * m->matrix_a[0][1]);
	m->matrix_aAdj[0][2] = m->matrix_a[0][1] * m->matrix_a[1][2] - m->matrix_a[1][1] * m->matrix_a[0][2];
	m->matrix_aAdj[1][2] = -(m->matrix_a[0][0] * m->matrix_a[1][2] - m->matrix_a[1][0] * m->matrix_a[0][2]);
	m->matrix_aAdj[2][2] = m->matrix_a[0][0] * m->matrix_a[1][1] - m->matrix_a[1][0] * m->matrix_a[0][1];

	return m;
}

Matriz inversa(Matriz m) {
	/* Una matriz solo es invertible si su determinante es distinto de 0. */

	/* La matriz inversa es igual a (1/|A|)*(Adj(A))^t */

	/* Construyo solo la matriz traspuesta de la adjunta. Cada
	 * elemento de la matriz sera el numerador de una fraccion que
	 * tendra por denominador el determinite de la matriz de entrada. */
	m->matrix_aInv[0][0] = m->matrix_aAdj[0][0];
	m->matrix_aInv[1][0] = m->matrix_aAdj[1][0];
	m->matrix_aInv[2][0] = m->matrix_aAdj[2][0];
	m->matrix_aInv[0][1] = m->matrix_aAdj[0][1];
	m->matrix_aInv[1][1] = m->matrix_aAdj[1][1];
	m->matrix_aInv[2][1] = m->matrix_aAdj[2][1];
	m->matrix_aInv[0][2] = m->matrix_aAdj[0][2];
	m->matrix_aInv[1][2] = m->matrix_aAdj[1][2];
	m->matrix_aInv[2][2] = m->matrix_aAdj[2][2];

	return m;
}

void printDatos(Matriz m) {

	/* Imprime la matriz de entrada. */
	printf("\nMatriz:\n");
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			printf("%3d", m->matrix_a[i][j]);
		}
		printf("\n");
	}

	/* Imprime su determinante. */
	printf("\nEl determinante es: %d\n", determinante(m));

	/* Imprime la matriz traspuesta. */
	printf("\nMatriz traspuesta:\n");
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			printf("%3d", m->matrix_aT[i][j]);
		}
		printf("\n");
	}

	/* Imprime la matriz adjunta. */
	printf("\nMatriz adjunta:\n");
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			printf("%3d", m->matrix_aAdj[i][j]);
		}
		printf("\n");
	}

	printf("\nMatriz inversa:\n");
	/* Solo se calculara la inversa si el determinante es distinto de cero. */
	if(determinante(m) != 0){
		inversa(m);

		/* Imprime la matriz inversa. */
		for (i = 0; i < 3; i++) {
			for (j = 0; j < 3; j++) {
				printf("%3d/%d", m->matrix_aInv[i][j], determinante(m));
			}
			printf("\n");
		}
	} else {
		printf("No tiene invertida por que su determinante es 0.\n");
	}
}

int main(void) {
	Matriz matriz = malloc(sizeof(struct MatrizStruct));

	/* Pide datos de la matriz 3X3 */
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			printf("Numero %d,%d: ", i + 1, j + 1);
			scanf("%d", &matriz->matrix_a[i][j]);
		}
	}

	/* Se calcula todo */
	determinante(matriz);
	traspuesta(matriz);
	adjunta(matriz);

	/* Se muestran todos los datos por pantalla. */
	printDatos(matriz);

	return 0;
}

Para compilarlo solo tenéis que guardar estos dos archivos en una carpeta y ejecutar lo siguiente:

gcc -O0 -g3 -Wall -c -MF"matriz.d" -MT"matriz.o" -o "matriz.o" "matriz.c"
gcc  -o "matriz" ./matriz.o

Una simulación de su ejecución sería la siguiente:

 

1 opinión en “Operaciones con matrices en C”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *