最近由于项目任务较少,手上有不少空闲的时间,所以抽空研究了一下矩阵行列式的算法。
先来说说行列式,以下摘自百度百科:
行列式在数学中,是由解线性方程组产生的一种算式。行列式的特性可以被概括为一个多次交替线性形式,这个本质使得行列式在欧几里德空间中可以成为描述“体积”的函数。
[1] 其定义域为nxn的矩阵A,取值为一个标量,写作det(A)或 | A | 。行列式可以看作是有向面积或体积的概念在一般的欧几里得空间中的推广。或者说,在n维欧几里得空间中,行列式描述的是一个线性变换对“体积”所造成的影响。无论是在线性代数、多项式理论,还是在微积分学中(比如说换元积分法中),行列式作为基本的数学工具,都有着重要的应用。 行列式概念最早出现在解线性方程组的过程中。十七世纪晚期,关孝和与莱布尼茨的著作中已经使用行列式来确定线性方程组解的个数以及形式。十八世纪开始,行列式开始作为独立的数学概念被研究。
十九世纪以后,行列式理论进一步得到发展和完善。矩阵概念的引入使得更多有关行列式的性质被发现,行列式在许多领域都逐渐显现出重要的意义和作用,出现了线性自同态和向量组的行列式的定义。
n阶行列式的计算公式:
设有n²个数,排列成n行n列的表
a11 a12 ... a1n
a21 a22 ... a2n
... ... ... ...
an1 an2 ... ann
作出表中位于不同行不同列的n个数的乘积,并冠以符号(-1)t,得到形如 (-1)t a1p1*a2p2 ... anpn的项,其中p1,p2,....pn为自然数1,2,...n的一个排列,t为这个排列的逆序数,由于这样的排列共有n!个,因此形如上式的项共有n!项,所有这n!项的代数和。
关于逆序数:
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。
知道了算法,现在考虑其编程实现:
首先需要一个能计算逆序数的函数,将其命名为 inverse
/** * Inverse number * * @param s * @return */ public static int inverse(int[] s) { int t = 0; for (int i = 0; i < s.length - 1; i++) for (int j = i + 1; j < s.length; j++) if (s[i] > s[j]) t++; return t; }
其实现相对简单,对于数组中的每一个数,判断它之后比它小的数,累加之后的总数即为逆序数。
其次需要一个能返回0~n-1的所有排列情况的函数,一共有n!种排列。要返回所有的排列并不难,我们希望的情况是能把0~n!-1这n!个数和n!种排列对应起来方便在计算项的时候进行调用。为了方便,先定义一个阶乘函数factorial
public static int factorial(int n) { return n == 0 ? 1 : n * factorial(n - 1); }
再来考虑一下思路,阶乘来源于排列,那么排列的问题也可以归为阶乘的问题。现在有下面一种思路:对于1~n(这里只是为了方便说成1~n,实际中考虑0~n-1更好)的排列,先让n在n个位置中挑选一个,将(1~n!)分为n段,每一段有(n-1)!个数,给定的数index在哪个段就挑选哪个位置,之后考虑用递归挑选剩下的位置。到最后没有位置的时候,就对应一种排列,每一个给定的index都对应一种唯一的排列,这就完成了我们愿望。
/** * * @param n * the serial number size * @param index * index of all arrangements * @return A possible order from 1 to n by the given index that is from 0 * to n! - 1 */ public static int[] order(int n, int index) { if (n < 1) throw new IllegalArgumentException("The size of number array could not less than 1"); if (index >= factorial(n) || index < 0) throw new IllegalArgumentException("The index could not be reached"); int[] nums = new int[n];//java数组在初始化时自动置0,不需要手动置0 fillArray(n, nums, index); return nums; } private static void fillArray(int n, int[] nums, int index) { if (n == 0) return; int fac = factorial(n - 1); int p = index / fac + 1; int i = -1;//此处为填充还未填充的位置,即值为0的位置 while (p > 0) { if (nums[++i] == 0) p--; } nums[i] = n; fillArray(n - 1, nums, index % fac); }
最后是行列式算法,有了上面的基础相对容易,注意此处的A矩阵应该经过验证,每一行都含有相同数目的项,即列数相等,事实上在我的实现中它是一个包装类。
public static float det(float[][] A) { float det = 0f; int m = A.length; int n = A[0].length; if (m != n) return det; int fac = factorial(n); for (int i = 0; i < fac; i++) { // 返回一个排列 int[] order = order(n, i); // 逆序数确定项的正负性 float item = (inverse(order) & 1) == 0 ? 1 : -1; for (int j = 0; j < n; j++) item *= A[j][order[j]-1]; det += item; } return det; }