我正在尝试计算矩阵(任何大小)的行列式,用于自编码/面试练习。我的第一次尝试是使用递归,这导致我进行以下实现:
import java.util.Scanner.*;
public class Determinant {
double A[][];
double m[][];
int N;
int start;
int last;
public Determinant (double A[][], int N, int start, int last){
this.A = A;
this.N = N;
this.start = start;
this.last = last;
}
public double[][] generateSubArray (double A[][], int N, int j1){
m = new double[N-1][];
for (int k=0; k<(N-1); k++)
m[k] = new double[N-1];
for (int i=1; i<N; i++){
int j2=0;
for (int j=0; j<N; j++){
if(j == j1)
continue;
m[i-1][j2] = A[i][j];
j2++;
}
}
return m;
}
/*
* Calculate determinant recursively
*/
public double determinant(double A[][], int N){
double res;
// Trivial 1x1 matrix
if (N == 1) res = A[0][0];
// Trivial 2x2 matrix
else if (N == 2) res = A[0][0]*A[1][1] - A[1][0]*A[0][1];
// NxN matrix
else{
res=0;
for (int j1=0; j1<N; j1++){
m = generateSubArray (A, N, j1);
res += Math.pow(-1.0, 1.0+j1+1.0) * A[0][j1] * determinant(m, N-1);
}
}
return res;
}
}
到目前为止,这一切都很好,它给了我一个正确的结果。现在我想通过利用多个线程来计算这个行列式值来优化我的代码。 我尝试使用Java Fork / Join模型对其进行并行化。这是我的方法:
@Override
protected Double compute() {
if (N < THRESHOLD) {
result = computeDeterminant(A, N);
return result;
}
for (int j1 = 0; j1 < N; j1++){
m = generateSubArray (A, N, j1);
ParallelDeterminants d = new ParallelDeterminants (m, N-1);
d.fork();
result += Math.pow(-1.0, 1.0+j1+1.0) * A[0][j1] * d.join();
}
return result;
}
public double computeDeterminant(double A[][], int N){
double res;
// Trivial 1x1 matrix
if (N == 1) res = A[0][0];
// Trivial 2x2 matrix
else if (N == 2) res = A[0][0]*A[1][1] - A[1][0]*A[0][1];
// NxN matrix
else{
res=0;
for (int j1=0; j1<N; j1++){
m = generateSubArray (A, N, j1);
res += Math.pow(-1.0, 1.0+j1+1.0) * A[0][j1] * computeDeterminant(m, N-1);
}
}
return res;
}
/*
* Main function
*/
public static void main(String args[]){
double res;
ForkJoinPool pool = new ForkJoinPool();
ParallelDeterminants d = new ParallelDeterminants();
d.inputData();
long starttime=System.nanoTime();
res = pool.invoke (d);
long EndTime=System.nanoTime();
System.out.println("Seq Run = "+ (EndTime-starttime)/100000);
System.out.println("the determinant valaue is " + res);
}
然而,在比较性能之后,我发现Fork / Join方法的性能非常糟糕,矩阵维度越高,它变得越慢(与第一种方法相比)。开销在哪里?任何人都可以阐明如何改善这一点吗?
答案 0 :(得分:1)
ForkJoin代码较慢的主要原因是它实际上是在序列化时引入了一些线程开销。要从fork / join中受益,你需要先1)先fork所有实例,然后2)等待结果。将“compute”中的循环拆分为两个循环:一个用于fork(将ParallelDeterminants的实例存储在一个数组中),另一个用于收集结果。
另外,我建议只在最外层进行分叉,而不是在任何内部分叉。您不希望创建O(N ^ 2)个线程。
答案 1 :(得分:1)
使用此类,您可以计算具有任何维度
的矩阵的行列式这个类使用许多不同的方法使矩阵成三角形,然后计算它的行列式。它可用于高维矩阵,如500 x 500甚至更多。这个课程的好处是,你可以得到BigDecimal 的结果所以没有无穷大,而你总能得到准确的答案。顺便说一句,使用许多不同的方法并避免递归导致更快的方式,更高的性能答案。希望它会有所帮助。
import java.math.BigDecimal;
public class DeterminantCalc {
private double[][] matrix;
private int sign = 1;
DeterminantCalc(double[][] matrix) {
this.matrix = matrix;
}
public int getSign() {
return sign;
}
public BigDecimal determinant() {
BigDecimal deter;
if (isUpperTriangular() || isLowerTriangular())
deter = multiplyDiameter().multiply(BigDecimal.valueOf(sign));
else {
makeTriangular();
deter = multiplyDiameter().multiply(BigDecimal.valueOf(sign));
}
return deter;
}
/* receives a matrix and makes it triangular using allowed operations
on columns and rows
*/
public void makeTriangular() {
for (int j = 0; j < matrix.length; j++) {
sortCol(j);
for (int i = matrix.length - 1; i > j; i--) {
if (matrix[i][j] == 0)
continue;
double x = matrix[i][j];
double y = matrix[i - 1][j];
multiplyRow(i, (-y / x));
addRow(i, i - 1);
multiplyRow(i, (-x / y));
}
}
}
public boolean isUpperTriangular() {
if (matrix.length < 2)
return false;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < i; j++) {
if (matrix[i][j] != 0)
return false;
}
}
return true;
}
public boolean isLowerTriangular() {
if (matrix.length < 2)
return false;
for (int j = 0; j < matrix.length; j++) {
for (int i = 0; j > i; i++) {
if (matrix[i][j] != 0)
return false;
}
}
return true;
}
public BigDecimal multiplyDiameter() {
BigDecimal result = BigDecimal.ONE;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (i == j)
result = result.multiply(BigDecimal.valueOf(matrix[i][j]));
}
}
return result;
}
// when matrix[i][j] = 0 it makes it's value non-zero
public void makeNonZero(int rowPos, int colPos) {
int len = matrix.length;
outer:
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
if (matrix[i][j] != 0) {
if (i == rowPos) { // found "!= 0" in it's own row, so cols must be added
addCol(colPos, j);
break outer;
}
if (j == colPos) { // found "!= 0" in it's own col, so rows must be added
addRow(rowPos, i);
break outer;
}
}
}
}
}
//add row1 to row2 and store in row1
public void addRow(int row1, int row2) {
for (int j = 0; j < matrix.length; j++)
matrix[row1][j] += matrix[row2][j];
}
//add col1 to col2 and store in col1
public void addCol(int col1, int col2) {
for (int i = 0; i < matrix.length; i++)
matrix[i][col1] += matrix[i][col2];
}
//multiply the whole row by num
public void multiplyRow(int row, double num) {
if (num < 0)
sign *= -1;
for (int j = 0; j < matrix.length; j++) {
matrix[row][j] *= num;
}
}
//multiply the whole column by num
public void multiplyCol(int col, double num) {
if (num < 0)
sign *= -1;
for (int i = 0; i < matrix.length; i++)
matrix[i][col] *= num;
}
// sort the cols from the biggest to the lowest value
public void sortCol(int col) {
for (int i = matrix.length - 1; i >= col; i--) {
for (int k = matrix.length - 1; k >= col; k--) {
double tmp1 = matrix[i][col];
double tmp2 = matrix[k][col];
if (Math.abs(tmp1) < Math.abs(tmp2))
replaceRow(i, k);
}
}
}
//replace row1 with row2
public void replaceRow(int row1, int row2) {
if (row1 != row2)
sign *= -1;
double[] tempRow = new double[matrix.length];
for (int j = 0; j < matrix.length; j++) {
tempRow[j] = matrix[row1][j];
matrix[row1][j] = matrix[row2][j];
matrix[row2][j] = tempRow[j];
}
}
//replace col1 with col2
public void replaceCol(int col1, int col2) {
if (col1 != col2)
sign *= -1;
System.out.printf("replace col%d with col%d, sign = %d%n", col1, col2, sign);
double[][] tempCol = new double[matrix.length][1];
for (int i = 0; i < matrix.length; i++) {
tempCol[i][0] = matrix[i][col1];
matrix[i][col1] = matrix[i][col2];
matrix[i][col2] = tempCol[i][0];
}
} }
此类从用户接收n×n的矩阵,然后计算它的行列式。它还显示了解决方案和最终的三角矩阵。
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Scanner;
public class DeterminantTest {
public static void main(String[] args) {
String determinant;
//generating random numbers
/*int len = 300;
SecureRandom random = new SecureRandom();
double[][] matrix = new double[len][len];
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
matrix[i][j] = random.nextInt(500);
System.out.printf("%15.2f", matrix[i][j]);
}
}
System.out.println();*/
/*double[][] matrix = {
{1, 5, 2, -2, 3, 2, 5, 1, 0, 5},
{4, 6, 0, -2, -2, 0, 1, 1, -2, 1},
{0, 5, 1, 0, 1, -5, -9, 0, 4, 1},
{2, 3, 5, -1, 2, 2, 0, 4, 5, -1},
{1, 0, 3, -1, 5, 1, 0, 2, 0, 2},
{1, 1, 0, -2, 5, 1, 2, 1, 1, 6},
{1, 0, 1, -1, 1, 1, 0, 1, 1, 1},
{1, 5, 5, 0, 3, 5, 5, 0, 0, 6},
{1, -5, 2, -2, 3, 2, 5, 1, 1, 5},
{1, 5, -2, -2, 3, 1, 5, 0, 0, 1}
};
*/
double[][] matrix = menu();
DeterminantCalc deter = new DeterminantCalc(matrix);
BigDecimal det = deter.determinant();
determinant = NumberFormat.getInstance().format(det);
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
System.out.printf("%15.2f", matrix[i][j]);
}
System.out.println();
}
System.out.println();
System.out.printf("%s%s%n", "Determinant: ", determinant);
System.out.printf("%s%d", "sign: ", deter.getSign());
}
public static double[][] menu() {
Scanner scanner = new Scanner(System.in);
System.out.print("Matrix Dimension: ");
int dim = scanner.nextInt();
double[][] inputMatrix = new double[dim][dim];
System.out.println("Set the Matrix: ");
for (int i = 0; i < dim; i++) {
System.out.printf("%5s%d%n", "row", i + 1);
for (int j = 0; j < dim; j++) {
System.out.printf("M[%d][%d] = ", i + 1, j + 1);
inputMatrix[i][j] = scanner.nextDouble();
}
System.out.println();
}
scanner.close();
return inputMatrix;
}}
答案 2 :(得分:1)
有一种计算矩阵行列式的新方法,您可以从here中阅读更多内容
并且我已经实现了一个简单的版本,没有简单的简单的java中没有花哨的优化技术或库,并且已经针对先前描述的方法进行了测试,平均速度提高了10倍
public class Test {
public static double[][] reduce(int row , int column , double[][] mat){
int n=mat.length;
double[][] res = new double[n- 1][n- 1];
int r=0,c=0;
for (int i = 0; i < n; i++) {
c=0;
if(i==row)
continue;
for (int j = 0; j < n; j++) {
if(j==column)
continue;
res[r][c] = mat[i][j];
c++;
}
r++;
}
return res;
}
public static double det(double mat[][]){
int n = mat.length;
if(n==1)
return mat[0][0];
if(n==2)
return mat[0][0]*mat[1][1] - (mat[0][1]*mat[1][0]);
//TODO : do reduce more efficiently
double[][] m11 = reduce(0,0,mat);
double[][] m1n = reduce(0,n-1,mat);
double[][] mn1 = reduce(n-1 , 0 , mat);
double[][] mnn = reduce(n-1,n-1,mat);
double[][] m11nn = reduce(0,0,reduce(n-1,n-1,mat));
return (det(m11)*det(mnn) - det(m1n)*det(mn1))/det(m11nn);
}
public static double[][] randomMatrix(int n , int range){
double[][] mat = new double[n][n];
for (int i=0; i<mat.length; i++) {
for (int j=0; j<mat[i].length; j++) {
mat[i][j] = (Math.random()*range);
}
}
return mat;
}
public static void main(String[] args) {
double[][] mat = randomMatrix(10,100);
System.out.println(det(mat));
}
}
在m11nn行列式的情况下有一点错误,如果碰巧为零,它将炸毁,您应该检查一下。我已经对100个随机样本进行了测试,但这种情况很少发生,但我认为值得一提,并且使用更好的索引方案也可以提高效率
答案 3 :(得分:0)
这是我的Matrix类的一部分,该类使用名为double[][]
的{{1}}成员变量来存储矩阵数据。
data
函数使用带有_determinant_recursivetask_impl()
的{{1}}对象来尝试使用多个线程进行计算。
与矩阵运算相比,此方法执行非常慢,无法获得上/下三角矩阵。例如,尝试计算13x13矩阵的行列式。
RecursiveTask<Double>
答案 4 :(得分:-1)
int det(int[][] mat) {
if (mat.length == 1)
return mat[0][0];
if (mat.length == 2)
return mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1];
int sum = 0, sign = 1;
int newN = mat.length - 1;
int[][] temp = new int[newN][newN];
for (int t = 0; t < newN; t++) {
int q = 0;
for (int i = 0; i < newN; i++) {
for (int j = 0; j < newN; j++) {
temp[i][j] = mat[1 + i][q + j];
}
if (q == i)
q = 1;
}
sum += sign * mat[0][t] * det(temp);
sign *= -1;
}
return sum;
}