这些值必须乘以当我
删除该值所在的行和列。
您必须乘以辅因子矩阵,其条目是删除第 i 行和第 j 列时留下的矩阵的行列式。
当然,这是递归算法的设置,因为较大矩阵的行列式用较小矩阵的行列式表示:如果A = (a_{ij}) 是矩阵,则det(A) = sum j = 1..n: a_{ij} * det(M_{ij}),其中M_{ij} 是删除 i-th 行和 j-th 列时由 A 产生的次要矩阵,其中 i 是固定的。基本情况是 2×2 矩阵,也可能是 3×3 矩阵。
出现的问题是n-by-n 矩阵产生n 矩阵M_{ij},大小为(n-1)-by-(n-1),每个矩阵都产生n-1 大小小于1 的矩阵,依此类推,直到达到基本情况,此时您必须跟踪n!/2 矩阵。 (在这一点上很明显,拉普拉斯展开是一种相当昂贵的算法,任何基于高斯消元的算法都会更有效。但这只是一个问题,因为我们正在讨论拉普拉斯展开。)如果以迭代方式完成,这必须手动完成,递归算法将通过堆栈帧进行隐式记账。
你的方法
让我们看一下您提供的这段代码。它使我无法理解您要达到的目标。以迭代col的最内层循环中的语句为例:
matrixFirstRowValues[row + (matrixSize - i)] = matrix[1][col + (matrixSize - i)];
只要最内层循环中的col 发生变化,row 和i 都是固定的,因此您正在从(显然)matrix 中的第二行分配和重新分配到 @ 中的同一条目987654340@。不仅如此,您从超出列范围的索引范围(matrixSize-i) .. (2*matrixSize - (i+1)) 分配,除非i == matrixSize,这仅适用于i 的第一个值。
正如我之前提到的,最终你不会只得到一个 2×2 矩阵,而是n!/2。
复制除第 i 行和第 j 列之外的内容
查看删除了i-th 行和j-th 列的矩阵,最终得到四个子矩阵(其中一些可能为空)。限制自己沿着第一行扩展,然后你只处理两个子矩阵(其中一些可能是空的)。您可以使用两个循环,一个用于j-th 列左侧和右侧的矩阵 - 或者,如上一个答案中所建议的 - 选择使用 continue 跳过 j-th 列到循环循环而不更新目标列索引。如果col 标记要删除的当前列(当前行始终为0),则在所有行上迭代r,在所有列上迭代c,并在c == col 处将列循环分成两部分。假设目标矩阵称为minor,那么它看起来像这样:
// expand along first row
for(col = 0; col < matrix_size; col++) {
// copy into minor matrix, left side then right side
for(r = 1; r < matrix_size; r++) {
for(c = 0; c < col; c++) {
minor[r-1][c] = matrix[r][c];
}
for(c = col+1; c < matrix_size; c++) {
minor[r-1][c-1] = matrix[r][c];
}
}
// use "minor" matrix at this point to calculte
// its determinant
}
索引移位r-1 是由于删除了第一行。
一个完整的递归拉普拉斯展开式
正如我之前提到的,行列式的拉普拉斯展开式自然适用于递归算法。我将对您的设置进行一些更改,我不会使用堆栈分配的可变长度数组,而是使用堆分配的内存。由于扩展,如果空间没有被重用,具有指数空间需求,对于中等大小的矩阵,堆栈可能很快就会耗尽。因此,我需要一个额外的参数来通过我称之为is_valid 的意图输出参数来报告内存分配失败。
你会认出上面的矩阵复制过程有一点不同的名字和一个额外的指针取消引用,因为我在堆上使用 n×n 矩阵。我希望它不会导致太多的混乱。
#include <stdio.h>
#include <stdlib.h>
#define SQR(x) ((x)*(x))
int laplace_det(int matrix_size, const int (*mat)[][matrix_size], int *is_valid) {
// base cases
if(matrix_size == 1)
return (*mat)[0][0];
if(matrix_size == 2)
return (*mat)[0][0] * (*mat)[1][1] - (*mat)[1][0] * (*mat)[0][1];
// recusive case, matrix_size > 2
// expansion indiscriminately along the first row
//
// minor matrix with i-th row and j-th column
// removed for the purpose of calculating
// the minor.
// r, c row and column index variables
// col current column in expansion
// d determinant accumulator
//
int r, c, col, d = 0;
int (*minor)[matrix_size-1][matrix_size-1] = calloc(SQR(matrix_size-1), sizeof(int));
if(!minor) {
*is_valid = 0;
return 0;
}
// expand along first row
for(col = 0; col < matrix_size; col++) {
// copy into minor matrix, left side then right side
for(r = 1; r < matrix_size; r++) {
for(c = 0; c < col; c++) {
(*minor)[r-1][c] = (*mat)[r][c];
}
for(c = col+1; c < matrix_size; c++) {
(*minor)[r-1][c-1] = (*mat)[r][c];
}
}
// calculate minor
int temp_d = laplace_det(matrix_size-1, minor, is_valid);
if(!is_valid) {
free(minor);
return 0;
}
d += (col & 1 ? -1 : 1) * (*mat)[0][col] * temp_d;
}
// free resources
free(minor);
return d;
}
示例驱动程序:
int main(void) {
int is_valid = 1;
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int det_m = laplace_det(3, &matrix, &is_valid);
if(is_valid) {
printf("determinant %d\n", det_m);
}
}
一种迭代方法
如果您想迭代地做同样的事情,您将需要为所有较小的n-1 子矩阵提供空间。正如递归案例所示,您可以为所有 相同 大小的子矩阵重用相同的空间,但不能将该空间用于更小尺寸的矩阵,因为每个矩阵都必须生成所有尺寸小一的子矩阵, 每列一个。
由于事先不知道矩阵的原始大小,以一般方式遍历所有这些矩阵很难实现,并且需要我们免费获得大量簿记,将这些值保留在递归的各自堆栈帧中案子。但我想将当前列选择器保存在一个大小为matrixSize 的数组中,以及一个指向子矩阵的指针数组中,这样可以迭代地重写它。