关于矩阵的特征值和特征向量求解,大部分的数学运算库都进行了提供,下面是使用MTL库的接口进行封装。
#include <mtl/matrix.h> #include <mtl/mtl.h> #include <mtl/dense1D.h> #include <mtl/utils.h> #include <mtl/lu.h> /*! 对角阵 */ typedef mtl::matrix <double, mtl::diagonal<>, mtl::dense<>, mtl::row_major>::type DiagMatrix; /*! 对称阵 */ typedef mtl::matrix <double, mtl::symmetric<mtl::lower>, mtl::packed<mtl::external>, mtl::column_major>::type SymmMatrix; /*! 标准矩阵,数值存在矩阵中 */ typedef mtl::matrix <double, mtl::rectangle<>, mtl::dense<>, mtl::row_major>::type Matrix; /*! 标准矩阵,数值存在外部 */ typedef mtl::matrix <double, mtl::rectangle<>, mtl::dense<mtl::external>, mtl::row_major>::type ExtMatrix; /*! 标准向量,数值存在矩阵中 */ typedef mtl::dense1D<double> Vector; /*! 标准向量,数值存在外部 */ typedef mtl::external_vec<double> ExtVector; /** * @brief 求实对称矩阵的特征值及特征向量 * @param MMatrix 所求的矩阵 * @param EigenValues 矩阵的特征值 * @param EigenVectors 矩阵的特征向量(按照列来存),如果特征值的序号为1,那么对应的特征向量为第1列 * @param Percent 矩阵的特征值百分比 * @param AccPercent 矩阵的特征值累积百分比 * @param deps 累计误差 * @return 返回值小于0表示超过迭代jt次仍未达到精度要求,返回值大于0表示正常返回 */ bool GetMatrixEigen(Matrix MMatrix, Vector &EigenValues, Matrix &EigenVectors, Vector *Percent = NULL, Vector *AccPercent = NULL, double deps = 0.00000001);下面是求解矩阵特征值和特征向量的实现:
/** * @brief 求实对称矩阵的特征值及特征向量的雅格比法 * 利用雅格比(Jacobi)方法求实对称矩阵的全部特征值及特征向量 * @param a 长度为n*n的数组,存放实对称矩阵,返回时对角线存放n个特征值 * @param n 矩阵的阶数 * @param v 长度为n*n的数组,返回特征向量(按列存储) * @param eps 控制精度要求 * @param jt 整型变量,控制最大迭代次数 * @return 返回false表示超过迭代jt次仍未达到精度要求,返回true表示正常返回 */ bool Eejcb(double a[], int n, double v[], double eps, int jt) { int i,j,p,q,u,w,t,s,l; double fm,cn,sn,omega,x,y,d; l=1; //初始化特征向量矩阵使其全为0 for(i=0; i<=n-1; i++) { v[i*n+i] = 1.0; for(j=0; j<=n-1; j++) { if(i != j) v[i*n+j]=0.0; } } while(true) //循环 { fm = 0.0; for(i=0; i<=n-1; i++) //找出,矩阵a(特征值),中除对角线外其他元素的最大绝对值 { //这个最大值是位于a[p][q] ,等于fm for(j=0; j<=n-1; j++) { d = fabs(a[i*n+j]); if((i!=j) && (d> fm)) { fm = d; p = i; q = j; } } } if(fm < eps) //精度复合要求 return true; //正常返回 if(l > jt) //迭代次数太多 return false;//失败返回 l ++; // 迭代计数器 u = p*n + q; w = p*n + p; t = q*n + p; s = q*n + q; x = -a[u]; y = (a[s]-a[w])/2.0; //x y的求法不同 omega = x/sqrt(x*x+y*y); //sin2θ //tan2θ=x/y = -2.0*a[u]/(a[s]-a[w]) if(y < 0.0) omega=-omega; sn = 1.0 + sqrt(1.0-omega*omega); sn = omega /sqrt(2.0*sn); //sinθ cn = sqrt(1.0-sn*sn); //cosθ fm = a[w]; // 变换前的a[w] a[p][p] a[w] = fm*cn*cn + a[s]*sn*sn + a[u]*omega; a[s] = fm*sn*sn + a[s]*cn*cn - a[u]*omega; a[u] = 0.0; a[t] = 0.0; // 以下是旋转矩阵,旋转了了p行,q行,p列,q列 // 但是四个特殊点没有旋转(这四个点在上述语句中发生了变化) // 其他不在这些行和列的点也没变 // 旋转矩阵,旋转p行和q行 for(j=0; j<=n-1; j++) { if((j!=p) && (j!=q)) { u = p*n + j; w = q*n + j; fm = a[u]; a[u] = a[w]*sn + fm*cn; a[w] = a[w]*cn - fm*sn; } } //旋转矩阵,旋转p列和q列 for(i=0; i<=n-1; i++) { if((i!=p) && (i!=q)) { u = i*n + p; w = i*n + q; fm = a[u]; a[u]= a[w]*sn + fm*cn; a[w]= a[w]*cn - fm*sn; } } //记录旋转矩阵特征向量 for(i=0; i<=n-1; i++) { u = i*n + p; w = i*n + q; fm = v[u]; v[u] =v[w]*sn + fm*cn; v[w] =v[w]*cn - fm*sn; } } return true; } bool GetMatrixEigen(Matrix MMatrix, Vector &EigenValues, Matrix &EigenVectors, Vector *Percent, Vector *AccPercent, double deps) { int nDem = MMatrix.nrows(); double *mat = new double[nDem*nDem]; //输入矩阵,计算完成之后保存特征值 double *eiv = new double[nDem*nDem]; //计算完成之后保存特征向量 for (int i=0; i<nDem; i++) //给输入矩阵和输出特征向量的矩阵赋值初始化 { for (int j=0; j<nDem; j++) { mat[i*nDem+j] = MMatrix(i,j); eiv[i*nDem+j] = 0.0; } } bool rel = Eejcb(mat, nDem, eiv, deps, 100); //计算特征值和特征向量 if (!rel) return false; Vector TmpEigenValues(nDem); //临时特征值 Matrix TmpEigenVectors(nDem,nDem); //临时特征向量 for (int i=0; i<nDem; i++) //赋值 { for (int j=0; j<nDem; j++) { TmpEigenVectors(i,j) = eiv[i*nDem+j]; if (i == j) TmpEigenValues[i] = mat[i*nDem+j]; } } delete []mat; delete []eiv; double dSumEigenValue = 0.0; //特征值总和 std::map<double, int> mapEigen; for (size_t index=0; index<TmpEigenValues.size(); index++) { mapEigen[TmpEigenValues[index]] = index; dSumEigenValue += TmpEigenValues[index]; } //对协方差矩阵的特征值和特征向量排序并计算累计百分比和百分比 std::map<double, int>::reverse_iterator iter = mapEigen.rbegin(); for(size_t ii=0; ii<TmpEigenValues.size(); ii++) { if(iter == mapEigen.rend()) break; EigenValues[ii] = iter->first; int index = iter->second; //获取该特征值对应的特征向量对应的列号 if(Percent != NULL) //计算百分比以及累积百分比 { double dTemp = iter->first / dSumEigenValue; (*Percent)[ii] = dTemp; if(AccPercent != NULL) { if(ii != 0) (*AccPercent)[ii] = (*AccPercent)[ii-1] + dTemp; else (*AccPercent)[ii] = dTemp; } } double dMaxValue = ABS(TmpEigenVectors(0, index)); int iNum = 0; for(int iRow=0; iRow<nDem; iRow++) //获取特征向量中绝对值最大的位置 { double dTemp = ABS(TmpEigenVectors(iRow, index)); if(dMaxValue < dTemp) { dMaxValue = dTemp; iNum = iRow; } } bool bIsPositive = false; //判断最大的特征向量中的值是否为正 if(dMaxValue-TmpEigenVectors(iNum, index)<0.000000001) bIsPositive = true; for(int iRow=0; iRow<nDem; iRow++) //确保每一个特征向量中的绝对值最大的都为正 { if(!bIsPositive) EigenVectors(iRow, ii) = -TmpEigenVectors(iRow, index); else EigenVectors(iRow, ii) = TmpEigenVectors(iRow, index); } iter++; } return true; }求解最核心的方法是雅可比方法。关于雅可比方法网上有很多资料,这里不再进行说明。
参考资料:
[1]http://osl.iu.edu/research/mtl/mtl2.php3
[2]https://zh.wikipedia.org/wiki/%E5%8D%A1%E7%88%BE%C2%B7%E9%9B%85%E5%8F%AF%E6%AF%94
[3]https://zh.wikipedia.org/wiki/%E9%9B%85%E5%8F%AF%E6%AF%94%E7%9F%A9%E9%98%B5
[4]http://boya.xmu.edu.cn/hhal/numchapt3/num_32.files/frame.htm