LU分解法:
最好的功能是在于它在解A*x = b,并且当有许多个A相同,b不同的式子时,效率较高。
算法思想(由VC++那本书上说,这个又叫杜利特尔Doolittle方法):
我们将A分解成为两个子矩阵L和U,令A = L * U。其中L是下三角矩阵,U是上三角矩阵。
大致可以得到如下(Tex公式):
[tex]\begin{pmatrix} L_{00} & 0 & 0 & 0 \\ L_{10} & L_{11} & 0 & 0 \\ L_{20} & L_{21} & L_{22} & 0 \\ L_{30} & L_{31} & L_{32} & L_{33} \end{pmatrix} \cdot \begin{pmatrix} U_{00} & U_{01} & U_{02} & U_{03} \\ 0 & U_{11} & U_{12} & U_{13} \\ 0 & 0 & U_{22} & U_{23} \\ 0 & 0 & 0 & U_{33} \end{pmatrix} = \begin{pmatrix} a_{00} & a_{01} & a_{02} & a_{03} \\ a_{10} & a_{11} & a_{12} & a_{13} \\ a_{20} & a_{21} & a_{22} & a_{23} \\ a_{30} & a_{31} & a_{32} & a_{33} \end{pmatrix}[/tex]
非Tex公式:
于是我们可以将整个线性方程化为如下形式:
A * x = (L * U) * x = L * (U * x) = b
先解出:
L * y = b
然后再解出:
U * x = y
即可得到最终解。
注:我们在储存L和U的时候,可以将两个矩阵储存在同一矩阵之中。
算法过程:
(1)A分解为L和U:
我们先将L[i,i]令为1,其中i = 0,.., N-1
对于for(j=0;j <= N-1; ++j),
for(i=0;i <= j; ++i)
[tex]U_{ij} = a_{ij} - \sum_{k=0}^{i-1}(L_{ik}*U_{kj})[/tex]
非tex公式:U[i,j] = a[i,j] - Sum[ L[i,k]*U[k,j],{k,0,i-1} ]
for(i=j+1;i <= N-1; ++i)
[tex]L_{ij} = \frac {1}{U_{jj}}[L_{ij} - \sum_{k=0}^{j-1}(L_{ik}*U_{kj})][/tex]
非tex公式:L[i,j] = 1/U[j,j]*(L[i,j] - Sum[ L[i,k]*U[k,j], {k,0,j-1} ]
(2)对于L * y = b解y得到:
非tex公式:y[0] = b[0]/L[0,0]; y[i] = 1/L[i,i]*(b[i] - Sum[ L[i,j] * y[j], {j,0,i-1} ]), i = 1, 2, ..., N-1
对于U * x = y解x得到:
非tex公式:x[N-1] = y[N-1]/U[N-1,N-1]; x[i] = 1/U[i,i]*(y[i] - Sum[ U[i,j] * x[j], {j,i+1,N-1} ], i = N-2, N-3, ..., 0
使用概述:
如果需要重复使用A矩阵来解不同b的线性方程时,我们在使用完第一次solve(b,x)之后,重新刷新b的值,再度调用solve(b,x)即可在x中获得新的解。如有不明白之处请查看本文中LUdecomposition_test1.cpp
Version 1.0
/** *@file LUdecomposition.h */ /** *LU Decomposition Introduction: *Solves "A * x = b" *(1) Let "L * U = A", shown as follow: * ( L00 0 0 0 ) ( U00 U01 U02 U03 ) ( a00 a01 a02 a03 ) * ( L10 L11 0 0 ) ( 0 U11 U12 U13 ) ( a10 a11 a12 a13 ) * ( L20 L21 L22 0 ) * ( 0 0 U22 U23 ) = ( a20 a21 a22 a23 ) * ( L30 L31 L32 L33 ) ( 0 0 0 U33 ) ( a30 a31 a32 a33 ) *(2) Solve "A * x = b" using a decomposition as: * A * x = (L * U) * x = L * (U * x) = b * then * L * y = b * then solve * U * x = y *(3) solve for L and U as follow: * L[i,0] * U[0,j] + ... = a[i,j] * set "a[i,i] = 1, i = 0, ..., N-1" * for i = 0, 1, ..., j, solve for U[i,j] as follow * U[i,j] = a[i,j] - Sum[L[i,k] * U[k,j], {k, 0, j-1}] * for i = j+1, j+2, ..., N-1 solve for L[i,j] as follow * L[i,j] = 1/U[j,j] * (a[i,j] - Sum[L[i,k] * U[k,j], {k, 0, j-1}]) *(4) forward substitution as follow: * y[0] = b[0]/L[0,0]; * y[i] = 1/L[i,i]*(b[i] - Sum[L[i,j] * y[j], {j, 0, i-1}]), i = 1, 2, ..., N-1 * backsubstitution as follow: * x[N-1] = y[N-1]/U[N-1,N-1] * x[i] = 1/U[i,i]*(y[i] - Sum[U[i,j] * x[j], {j, i+1, N-1}]), i = N-2, N-3, ..., 0 *(5) save the L and U in a matrix as follow: * ( U00 U01 U02 U03 ) * ( L10 U11 U12 U13 ) * ( L20 L21 U22 U23 ) * ( L30 L31 L32 U33 ) * Matrix--2.3.14 */ #ifndef LUDECOMPOSITION_H_INCLUDED #define LUDECOMPOSITION_H_INCLUDED /** *@brief Object for solving linear equations A*x = b using LU decomposition, and related functions. */ struct LUdecomposition{ Int n; MatDoub lu; ///Stores the decomposition. VecInt indx; ///Stores the permutation. Doub det_d; ///Used by det. LUdecomposition(MatDoub_I &a); ///Constructor. Argument is the matrix A. void solve(VecDoub_I &b, VecDoub_O &x); ///Solve for a single right-hand side. void solve(MatDoub_I &b, MatDoub_O &x); ///Solve for multiple right-hand sides. void inverse(MatDoub_O &a_inverse); ///Calculate matrix inverse A^(-1). Doub det(); ///Return determinant of A. void mprove(VecDoub_I &b, VecDoub_IO &x); MatDoub_I &aref; ///Used only by mprove. }; /** *@brief Constructor *@param a[0..n-1][0..n-1] Input as Matrix A in A * x = b, and Output a as after LU decomposition. *Input: * Given a matrix a[0..n-1][0..n-1], this routine replaces it by the LU decomposition of a * rowwise permutation of itself. "a" is input. *Output: * "a" is arranged as in "Matrix--2.3.14" above; * indx[0..n-1] is an output vector that records the row permutation effected by * the partial pivoting * d is output as 1 or -1 depending on whether the number of row interchanges * was even or odd, respectively. *This routine is used in combination with solve to solve linear equations or invert a matrix. */ LUdecomposition :: LUdecomposition(MatDoub_I &a) : n(a.nrows()), lu(a), aref(a), indx(n) { const Doub TINY = 1.0e-40; //A small number; Int i, imax, j, k; Doub big, temp; VecDoub vv(n); //vv stores the implicit scaling of each row. det_d = 1.0; //No row interchages yet. for(i = 0; i < n; ++i){ //Loop over rows to get the implicit scaling information big = 0.0; for(j = 0; j < n; ++j){ if((temp = abs(lu[i][j])) > big) big = temp; }//for if(big == 0.0) //No nonzero largest element. NRthrow("Singular matrix in LUdecomposition"); vv[i] = 1.0/big; }//for for(k = 0; k < n; ++k){ //This is the outermost k[j,j] loop. big = 0.0; //Initialize for the search for largest pivot element. for(i = k; i < n; ++i){ temp = vv[i] * abs(lu[i][k]); if(temp > big){ //Is the figure of merit for the pivot better than the best so far? big = temp; imax = i; }//if }//for if(k != imax){ //Interchange rows for(j = 0; j < n; ++j){ temp = lu[imax][j]; lu[imax][j] = lu[k][j]; lu[k][j] = temp; }//for det_d = -det_d; //change the parity of d vv[imax] = vv[k]; //interchange the scale factor. }//if indx[k] = imax; if(lu[k][k] == 0.0) lu[k][k] = TINY; /* *If the pivot element is zero, the matrix is singular (at least to the precision of *the algorithm). For some applications on singular matrices, it is desirable to *substitute TINY for zero. */ for(i = k+1; i < n; ++i){ temp = lu[i][k] /= lu[k][k]; //Divide by the pivot element. for(j = k+1; j < n; ++j){ //Innermost loop: reduce remaining submatrix. lu[i][j] -= temp * lu[k][j]; }//for }//for }//for }//LUdecomposition(MatDoub_I &a) /** *@brief LU Decomposition solve A * x = b for a single right-hand side. *Solves the set of n linear equations A * x = b using the stored LU decomposition of A. *b[0..n-1] is input as the right-hand side vector b, while x returns the solution vector x; *b and x may reference the same vector, in which case the solution overwrites the input. *This routine takes into account the possibility that b will begin with many zero elements, *so it is efficient for use in matrix inversion. * *Input: *@param b[0..n-1] Input as right-hand side vector *Output: *@param x[0..n-1] Output as solution vector x of A^(-1) * b */ void LUdecomposition :: solve(VecDoub_I &b, VecDoub_O &x){ Int i, ii = 0, ip, j; Doub sum; if(b.size() != n || x.size() != n) NRthrow("LUdecomposition : solve bad sizes. The right-hand vector should be the same as" "the solution vector."); for(i = 0; i < n; ++i) x[i] = b[i]; for(i = 0; i < n; ++i){ /* *When ii is set to a positive value, it will become the index of the first *nonvanishing element of b. We now do the forward substitution in Information Step(4). *The only new small problem is to get the permutation as we go. *( * If you read the "next_permutation" function of STL in "stl_algo.h", you will find a similar * algorithm. *) */ ip = indx[i]; sum = x[ip]; x[ip] = x[i]; if(ii != 0){ for(j = ii-1; j < i; ++j) sum -= lu[i][j] * x[j]; }//if else if(sum != 0.0){ //A nonzero element was encountered, so from now we will have to do the sums in the loop above. ii = i+1; }//else if x[i] = sum; }//for for(i = n-1; i >= 0; --i){ //Now we do the backsubstitution in Information Step(4). sum = x[i]; for(j = i+1; j < n; ++j) sum -= lu[i][j] * x[j]; x[i] = sum/lu[i][i]; //Store a component of the solution vector X. }//for }//LUdecomposition :: solve(VecDoub_I &b, VecDoub_O &x) /** *@brief LU Decomposition solve for multiple right-hand sides. *Solves m sets of n linear equations A * X = B using the stored LU decomposition of A. *The matrix b[0..n-1][0..m-1] inputs the right-hand sides, while x[0..n-1][0..m-1] returns *the solution A^(-1) * B. *@attention b and x may reference the same matrix, in which case the solution overwrites the input. * *Input: *@param b[0..n-1][0..m-1] Input as right-hand side matrix *Output: *@param x[0..n-1][0..m-1] Output as solution matrix x of A^(-1) * B */ void LUdecomposition :: solve(MatDoub_I &b, MatDoub_O &x){ Int i, j, m = b.ncols(); if(b.nrows() != n || x.nrows() != n || b.ncols() != x.ncols()) NRthrow("LUdecomposition :: solve bad sizes. The right-hand matrix should be the same as" "the solution matrix."); VecDoub xx(n); for(j = 0; j < m; ++j){ //Copy and solve each column in turn. for(i = 0; i < n; ++i) xx[i] = b[i][j]; solve(xx,xx); for(i = 0; i < n; ++i) x[i][j] = xx[i]; }//for }//LUdecomposition :: solve(MatDoub_I &b, MatDoub_O &x) /** *@brief Using the stored LU decomposition, return in a_inverse the matrix inverse, A^(-1). *@attention the matrix inverse, A^(-1) is in a_inverse. */ void LUdecomposition :: inverse(MatDoub_O &a_inverse){ Int i,j; a_inverse.resize(n,n); for(i = 0;i < n; ++i){ for(j = 0; j < n; ++j) a_inverse[i][j] = 0.; a_inverse[i][i] = 1.; } solve(a_inverse, a_inverse); } /** *@brief Using the stored LU decomposition, return the determinant of the matrix A. *@return the determinant of the matrix A */ Doub LUdecomposition :: det(){ Doub dd = det_d; for(Int i = 0; i < n; ++i) dd *= lu[i][i]; return dd; } #endif // LUDECOMPOSITION_H_INCLUDED
以下为对LUdecomposition.h头文件的测试文件,仅供参照:
/** *@file LUdecomposition_test.cpp */ #include <iostream> #include "nr3.h" #include "LUdecomposition.h" using namespace std; int main(){ MatDoub_IO a,b,x; int rows, cols; //INPUT DATA: cout << "Please input rows, cols and the matrix data for A: " << endl; cin >> rows >> cols; a.resize(rows, cols); readMatrix_console(a); //Do the Linear Solve; a and b will not be altered. {//Start a temporary scope. LUdecomposition lu(a); //Read matrix B1: cout << "Please input rows, cols and the matrix data for B: " << endl; cin >> rows >> cols; b.resize(rows, cols); x.resize(rows, cols); readMatrix_console(b); lu.solve(b,x); //PRINT: cout << "Gives the solution matrix: " << endl; printMatrix_console(x); //Read matrix B2: cout << "Please input rows, cols and the matrix data for B: " << endl; cin >> rows >> cols; b.resize(rows, cols); x.resize(rows, cols); readMatrix_console(b); //PRINT: cout << "Gives the solution matrix: " << endl; printMatrix_console(x); }//destroy alu. return 0; }
Version 2.0
mprove方法的实现:
功能:
改进已知近似解的精度,既可用于求一般方程组的高精度解,也可以用于病态方程组的求解,复杂度O(n^2)。
算法原理:
对于A * x = b(记为①式),其中x表示精确解。你使用上述LU-decomposition的solve方法得到一个近似解x+△x,于是如果你将x+△x与矩阵A相乘,得到的将是一个近似的右端项b+△b,如:“A * (x+△x) = (b+△b)”,记为②式。
我们将②式减去①式,得到“A * △x = △b”记为③式。
我们将②式带入③式,替换△b,则得到:A * △x = A * (x+△x) - b(记为④式),代码中我们将A * (x+△x) - b记为sdp。
我们对④式再进行LUdecomposition分解,即代码中的solve(r, r),得到△x。
最后将△x从x中减去即可。
测试方法:
对于由上lu.solve(b,x)解出的值x解向量,加上一个扰动值(可以由随机数生成)。
然后执行lu.mprove(b,x);进行解的优化,最后再输出,看是否得到正确的解。
提醒:
由于mprove的复杂度只有O(n^2),相比于solve的复杂度O(n^3)要小很多,建议在每次执行solve之后都执行1~2次的mprove,可根据精度要求增加次数。
/** *@brief Improve a solution vector x[0..n-1] of the linear set of equations A * x = b. *@param b the right-hand side vector as input *@param x Input as the original solution, Output as the improved solution. */ void LUdecomposition::mprove(VecDoub_I &b, VecDoub_IO &x){ Int i, j; VecDoub r(n); for(i=0; i < n; ++i){ //Calculate the right-hand side, accumulating the residual in higher precision. Ldoub sdp = -b[i]; for(j=0; j < n; ++j) sdp += (Ldoub)aref[i][j] * (Ldoub)x[j]; r[i] = sdp; }//for solve(r,r); //Solve for the error term. for(i = 0; i < n; ++i) //Subtract it from the old solution. x[i] -= r[i]; } //mprove(VecDoub_I &b, VecDoub_IO &x)