数值分析(五):C++实现一般实矩阵的QR分解

先简单介绍一下一般实矩阵的QR分解是什么;
再是源码,不过这次的C++源码是教材上的,提前说明,才五十几行代码就实现了一般实矩阵的QR分解,精炼且巧妙,若是我来写肯定是办不到这么简洁的,正如《C++ Primer》里作者所提倡的简洁是一种美德,这些代码简直就可以修身齐家治国平天下了;
然后再执行这个程序,得到结果,到后面再仔细地分析这个算法的过程(我自己清楚了,可能表达上也会无法传达,因为可能博客这东西更多的可能是写给自己看的······)。

矩阵的QR分解,就是说一个实矩阵,只要它的列向量线性无关,就可以进行QR分解,其中Q是正交矩阵,R是上三角矩阵,一个实矩阵(不一定是方阵),列向量线性无关就可以分解成一个正交矩阵和上三角矩阵的乘积,这就是一般实矩阵的QR分解。这个分解乍看不难,但是仔细计算起来却有难度,因为正交矩阵虽然在线性代数里听得多,见得多,但是实际用得不多,这次求矩阵特征值,整个一章就提到了两个正交矩阵,一个就是初等反射矩阵H,这是今天最关键的哥们,因为矩阵本身就是映射吗,而且这个映射比较特殊,一个向量经过这个矩阵处理,发现得到的向量大小不变,并且是关于某一个平面镜像对称的,这个平面就和H矩阵有关,另一个正交矩阵是旋转矩阵,这里不再细说。

知道什么是一般实矩阵的QR分解了,就给出源码,按照函数的观点,输入的参数是一个矩阵,这里实际上是二重指针,直接在函数体里面将矩阵的分解结果在黑框框里打印出来,以下是源码,以及执行的结果:

#include
using namespace std;

//接下来把一般实矩阵的QR分解按函数的形式稍稍改写一下,输入是一般mxn实矩阵A,以及矩阵的行数m列数n,输出是QR形式的正交矩阵和上三角矩阵的乘积,

void Maqr(double ** A, int m, int n)//进行一般实矩阵QR分解的函数
{
	int i, j, k, nn, jj;
	double u, alpha, w, t;
	double** Q = new double*[m];   //动态分配内存空间
	for (i = 0; i u) u = w;
		}
		alpha = 0.0;
		for (i = k; i <= m - 1; i++)
		{
			t = A[i][k] / u; alpha = alpha + t * t;
		}
		if (A[k][k] > 0.0) u = -u;
		alpha = u * sqrt(alpha);
		if (fabs(alpha) + 1.0 == 1.0)
		{
			cout << "\nQR分解失败!" << endl;
			exit(1);
		}

		u = sqrt(2.0*alpha*(alpha - A[k][k]));
		if ((u + 1.0) != 1.0)
		{
			A[k][k] = (A[k][k] - alpha) / u;
			for (i = k + 1; i <= m - 1; i++) A[i][k] = A[i][k] / u;
			
			//以上就是H矩阵的求得,实际上程序并没有设置任何数据结构来存储H矩
			//阵,而是直接将u向量的元素赋值给原A矩阵的原列向量相应的位置,这样做
			//这样做是为了计算左乘矩阵Q和A
			for (j = 0; j <= m - 1; j++)
			{
				t = 0.0;
				for (jj = k; jj <= m - 1; jj++)
					t = t + A[jj][k] * Q[jj][j];
				for (i = k; i <= m - 1; i++)
					Q[i][j] = Q[i][j] - 2.0*t*A[i][k];
			}
//左乘矩阵Q,循环结束后得到一个矩阵,再将这个矩阵转置一下就得到QR分解中的Q矩阵
//也就是正交矩阵

			for (j = k + 1; j <= n - 1; j++)
			{
				t = 0.0;
				for (jj = k; jj <= m - 1; jj++)
					t = t + A[jj][k] * A[jj][j];
				for (i = k; i <= m - 1; i++)
					A[i][j] = A[i][j] - 2.0*t*A[i][k];
			}
			//H矩阵左乘A矩阵,循环完成之后,其上三角部分的数据就是上三角矩阵R
			A[k][k] = alpha;
			for (i = k + 1; i <= m - 1; i++)  A[i][k] = 0.0;
		}
	}
	for (i = 0; i <= m - 2; i++)
		for (j = i + 1; j <= m - 1; j++)
		{
			t = Q[i][j]; Q[i][j] = Q[j][i]; Q[j][i] = t;
		}
	//QR分解完毕,然后在函数体里面直接将Q、R矩阵打印出来
	for (i = 0; i> m >> n;
	double** A = new double* [m];
	for (int i = 0; i < m; i++)A[i] = new double[n];
	cout << "输入矩阵A的每一个元素" << endl;
	for (int i = 0; i < m; i++)
		for (int j = 0; j < n; j++)
			cin >> A[i][j];
	Maqr(A, m, n);

	system("pause");
	return 0;
}

数值分析(五):C++实现一般实矩阵的QR分解_第1张图片

然后更仔细地分析矩阵的QR分解:
先用自然语言说一遍,自然语言讲究传神,传达最大的轮廓,用我的理解来说一遍:一般实矩阵的QR分解,Q是正交矩阵,R是上三角矩阵,给定一个mxn的实矩阵,列向量线性无关,所以就可以分解成mxm的正交矩阵,和mxn的上三角矩阵。

最重要的一步,就是找到n-1个初等反射矩阵H把A对角线以下的列向量全部化成单位向量(或是其倍数,方向由原向量中的最大的元素的符号所决定),这样乘一个H矩阵就把相应的一列对角线以下元素化成了0,这个A矩阵有n列,就要求n-1个这样的H矩阵,然后是不断左乘A矩阵,直到最后成了一个上三角矩阵,也就是所求的上三角矩阵R,那么Q矩阵呢?Q矩阵的就是所有H矩阵的累乘得到的矩阵,最后再求逆,不过,好在这些正交矩阵相乘仍然是正交矩阵,所以只要把这些累乘得到的矩阵转置一下就是Q矩阵;

然后结合图再说一遍:
数值分析(五):C++实现一般实矩阵的QR分解_第2张图片
矩阵QR分解的过程,就是找到n-1个初等反射矩阵H,然后左乘A矩阵,使得红线所圈住的向量全部化成只有首元素不为0的单位向量(或是其倍数,,,),写成公式就是:
数值分析(五):C++实现一般实矩阵的QR分解_第3张图片
这就是矩阵的QR分解。可见,其中最关键的就是初等反射矩阵H,要根据原向量找到这么一个初等反射矩阵H,将原向量映射成一个与仅首元素不为0的单位向量平行的向量,那么怎么找H矩阵呢?

设向量w满足w(转置)w=1,则矩阵H=I-2ww(转置),就是初等反射矩阵H,也有一个比较有逼格名称,叫豪斯霍尔德变换,这个矩阵写完整就是:
数值分析(五):C++实现一般实矩阵的QR分解_第4张图片
所以只要向量u非零,那么矩阵:
在这里插入图片描述
也是初等映射矩阵,毕竟就是系数的问题吗,很容易理解,那么怎么根据原向量,来找到这么一个特殊的矩阵?

这个矩阵有非常重要的几何意义,那就是一个向量经过这个矩阵的作用,得到一个新向量,新旧向量是镜面对称的,镜面就是w向量的法平面,所以就有这么一个约化定理,对于非零向量x,总是存在这么一个初等反射矩阵,将其映射成单位向量,即Hx=-a(1,0,0···,0);
这个a就是单位向量的倍数,数值就是x向量的模,刚刚也说了新旧向量是镜面对称的,自然向量长度是相等,然后根据向量x计算得到u向量,大小就是原向量的模,方向和x中元素最大的那个方向一致进而得到H矩阵的公式如下:
数值分析(五):C++实现一般实矩阵的QR分解_第5张图片

所以在本次编程中的第一步,就是计算H矩阵。因为要计算n-1个这样的H矩阵,所以接下的代码都是写在一个k从1~n-1的循环里面的(教材这里的u和上面的向量w是一样的,而与上面的模不是1的u向量有区别)。

数值分析(五):C++实现一般实矩阵的QR分解_第6张图片
是的,计算公式有点复杂,理解也有点难度,不过抓住H矩阵的几何意义就很好理解了,我怕说太多反而会造成误解,所以就直接上图了,σ是原向量的模,ρ是和H计算公式中的β是意义一样的,目的就是得到模为1的u向量,所以就是一个系数的问题。

然后是不断用H矩阵左乘Q(Q矩阵的初始值就是一个单位矩阵),所以就相当于H矩阵自身的不断累乘,最后转置这个矩阵就得到了正交矩阵Q,这也是这段代码比较吊诡,也是最巧妙的地方,纵使求了这么多遍H矩阵,代码中自始至终都没有出现存储H矩阵的数据结构,上面的截图也提示了,计算H矩阵,是通过计算u向量的,她计算出u向量,直接将u向量的元素,u1,u2,,,这些元素赋值给A矩阵的相应的位置,然后进行左乘Q,公式如下:
数值分析(五):C++实现一般实矩阵的QR分解_第7张图片
这样赋值的结果,竟然和左乘H矩阵的公式是一样的,笔者用纸笔验证过了,哈哈哈,其实能够这样写,而不按照矩阵乘法公式写的原因主要还是H矩阵的特殊性造成的,毕竟是对称的正交矩阵,这样的矩阵很特殊了;(2333333~,若是我来写这个算法的话,我估计还要老老实实存储一下H矩阵)
接着,再左乘A矩阵,同样的公式:
数值分析(五):C++实现一般实矩阵的QR分解_第8张图片
循环了n-1次之后最后得到一个矩阵不是一个上三角矩阵,但是她的对角线以上的数据就是R的数据,因为刚刚也说了对角线一下的数据是用于计算H矩阵的u向量的数据,至于最后不断累乘之后,成了什么样,也不需要关注了,只要把累乘后的矩阵的对角线以上的部分提取出来就可以了。
这样在一个大循环里就完成了一般实矩阵的QR分解啦~
哎呀呀呀,感觉没讲清楚啊,哈哈哈~
这么大段的文字,结果道理又没讲清楚,真是哭笑不得了,下次还是不写这么大段的文字了,估计还是自己太懒了~
两本教材是《数值分析第五版》,以及《常用算法程序集-C++语言描述(第四版)》(清华大学出版社),这两本书学习起来,绝搭。

你可能感兴趣的:(数值分析)