高斯消元法

Posted 会敲键盘的猩猩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高斯消元法相关的知识,希望对你有一定的参考价值。

高斯消元法,是线性代数中的一个算法,可用来求解线性方程组,并可以求出矩阵的秩,以及求出可逆方阵的逆矩阵。
高斯消元法的原理是:
若用初等行变换将增广矩阵 化为 ,则AX = B与CX = D是同解方程组。

所以我们可以用初等行变换把增广矩阵转换为行阶梯阵,然后回代求出方程的解。

1、线性方程组

                          

1)构造增广矩阵,即系数矩阵A增加上常数向量b(A|b)

                          

2)通过以交换行、某行乘以非负常数和两行相加这三种初等变化将原系统转化为更简单的三角形式(triangular form)

     注:这里的初等变化可以通过系数矩阵A乘上初等矩阵E来实现

                         

3)从而得到简化的三角方阵组,注意它更容易解

                         

4)这时可以使用向后替换算法(Algorithm for Back Substitution)求解得

    z=-4/-4=1,  y=4-2z=4-2=2,  x= (1-y-z)/2=(1-2-1)/2=-1

 

总结上面过程,高斯消元法其实就是下面非常简单的过程

                            原线性方程组       ——>       高斯消元法     ——> 下三角或上三角形式的线性方程组           ——>  前向替换算法求解(对于上三角形式,采用后向替换算法)

                                 

补充1:

高斯-若尔当消元法(Gauss-Jordan Elimination

相对于高斯消元法,高斯-若尔当消元法最后的得到线性方程组更容易求解,它得到的是简化行列式。其转化后的增高矩阵形式如下,因此它可以直接求出方程的解,而无需使用替换算法。但是,此算法的效率较低。

                             

 

例子如下:

          解为

个人感觉区别就是对每行进行了归一化处理

补充2:

介绍了最基本的高斯消元法,现在看看应用于实际问题的实用算法

因为实际应用中,我们总是利用计算机来分析线性系统,而计算机中以有限的数来近似无限的实数,因此产生舍入误差(roundoff error),进而对解线性系统产生很多影响。

一个t位(即精度为t)以为基的浮点数的表达形式为:。对于一个实数x,其浮点近似值为最接近x的浮点数,必要时进行近似

例1:对2位以10为基的浮点算法,

例2:同样考虑

 

以下面系统为例,看看在高斯消元中采用浮点算法会产生什么效果。

                                                                           

当以精确解法时,通过将第一行乘以m=89/47,并从第二行中减去得到,进而利用后向替换算法得x=1,y=-1。

当以3位以10为基的浮点算法时,乘子变为,因为,因此第一步高斯消元后得

。此时,因为不能将第2行第1列位置变为0,所以不能将其三角化。从而,我们只能接受将这个位置值赋为0,而不管其实际浮点值。因此,3位浮点高斯消元的结果为,后向算法计算结果为

尽管无法消除近似误差的影响,可以采用一些技术来尽量减小这类机器误差。部分主元消元法在高斯消元的每一步,都选择列上最大值为轴(通过行变换将其移动)。

下面给出列主元消去的代码(所谓列主元消去法是在系数矩阵中按列选取元素绝对值得最大值作为主元素,然后交换所在行与主元素所在行的位置,再按顺序消去法进行消元。)
列选主元消元法
/*
*Gauss.cpp
*功能:列选主元消元法 
*/
#include<stdio.h>
#include"Gass.h"

//高斯消元法(列选主元)
void Gauss (double a[][MAXNUM],int n)

	int i,j;
	SelectColE(a,n);   //列选主元并消元成上三角
	//回代求解
	printf("上三角的结果\\n");
	printM(a,3);
	for(i=n;i>=1;i--)
	
		for(j=i+1;j<=n;j++)
			a[i][n+1]-=a[i][j]*a[j][n+1];
		a[i][n+1]/=a[i][i];
	
	return ;

//选择列主元并进行消元
void SelectColE(double a[][MAXNUM],int n)

	int i,j,k,maxRowE;
	double temp; //用于记录消元时的因数
	for(j=1;j<=n;j++)
	
		maxRowE=j;
		for(i=j;i<=n;i++)
			if(fabs(a[i][j])>fabs(a[maxRowE][j]))
				maxRowE = i;
		if(maxRowE!=j)
			swapRow(a,j,maxRowE,n);   //与最大主元所在行交换
		//消元
		for(i=j+1;i<=n;i++)
		
			temp =a[i][j]/a[j][j];
			for(k=j;k<=n+1;k++)
				a[i][k]-=a[j][k]*temp;
		
		printf("第%d列消元后:\\n",j);
		printM(a,3);
	
	return;

void swapRow(double a[][MAXNUM],int m,int maxRowE,int n)

	int k;
	double temp;
	for(k=m;k<=n+1;k++)
	
		temp = a[m][k];
		a[m][k] = a[maxRowE][k];
		a[maxRowE][k] = temp;
	
	return ;


//测试函数
int main()

    double a[4][MAXNUM];
	
	int i,n,j;

    a[1][1] = 0.001; a[1][2]=2.000;a[1][3]=3.000;a[1][4]=1.000;
    a[2][1] = -1.000;a[2][2]=3.712;a[2][3]=4.623;a[2][4]=2.000;
    a[3][1] = -2.000;a[3][2]=1.070;a[3][3]=5.643;a[3][4]=3.000;
	Gauss (a,3);
	for(i=1;i<=3;i++)
		printf("X%d=%f\\n",i,a[i][4]);
	return 0;

//输出矩阵
void printM(double a[][MAXNUM], int n)

   int i,j;
   for(i=1;i<=n;i++)
	
		for(j=1;j<=n+1;j++)
		
			printf("%f ,",a[i][j]);
		
        printf("\\n");
	


测试结果:


2、逆矩阵



下面来说说高斯消元法在编程中的应用。

首先,先介绍程序中高斯消元法的步骤:
(我们设方程组中方程的个数为equ,变元的个数为var,注意:一般情况下是n个方程,n个变元,但是有些题目就故意让方程数与变元数不同)

1. 把方程组转换成增广矩阵。

2. 利用初等行变换来把增广矩阵转换成行阶梯阵。
枚举k从0到equ – 1,当前处理的列为col(初始为0) ,每次找第k行以下(包括第k行),col列中元素绝对值最大的列与第k行交换。如果col列中的元素全为0,那么则处理col + 1列,k不变。

3. 转换为行阶梯阵,判断解的情况。

① 无解
当方程中出现(0, 0, …, 0, a)的形式,且a != 0时,说明是无解的。

② 唯一解
条件是k = equ,即行阶梯阵形成了严格的上三角阵。利用回代逐一求出解集。

③ 无穷解。
条件是k < equ,即不能形成严格的上三角形,自由变元的个数即为equ – k,但有些题目要求判断哪些变元是不缺定的。
    这里单独介绍下这种解法:
首先,自由变元有var - k个,即不确定的变元至少有var - k个。我们先把所有的变元视为不确定的。在每个方程中判断不确定变元的个数,如果大于1个,则该方程无法求解。如果只有1个变元,那么该变元即可求出,即为确定变元。

以上介绍的是求解整数线性方程组的求法,复杂度是O(n3)。浮点数线性方程组的求法类似,但是要在判断是否为0时,加入EPS,以消除精度问题。

实例代码:

有多组测试数据。每组测试数据先输入一个整数n,表示方阵的阶。然后下面输入n阶方阵。输出其逆矩阵。若无逆矩阵,则输出No inverse matrix。

#include <iostream>
#include <cmath>
#include <algorithm>

using namespace std;

const double eps = 1e-6;

bool is_zero( const double num )//用于判断有无逆矩阵

    return fabs(num) < eps;


void create( double ** & matrix, const int n )

    matrix = new double* [n];
    for ( int i = 0; i < n; ++i )
        matrix[i] = new double[n];


void input ( double ** matrix, const int n )

    for ( int i = 0; i < n; ++i )
    
        for ( int j  = 0; j < n; ++ j )
            cin >> matrix[i][j];
    


bool inverse ( double ** matrix1, double ** matrix2, const int n )

    int i, j;
    for ( i = 0; i < n; ++ i )//初始化一个单位矩阵
    
        for ( j = 0; j < n; ++ j )
        
            if ( i == j )
                matrix2[i][j] = 1;
            else
                matrix2[i][j] = 0;
        
    
    for ( i = 0; i < n; ++i )
    
        int rowmaxpos = i;
        for ( j = i + 1; j < n; ++j )
        
            if ( matrix1[i][j] > matrix1[i][rowmaxpos] )
                rowmaxpos = j;
        
        for ( j = i; j < n; ++ j )//按从大到小的顺序排列矩阵
        
            swap( matrix1[j][rowmaxpos], matrix1[j][i]);
            swap( matrix2[j][rowmaxpos], matrix2[j][i]);
        
        if ( !is_zero(matrix1[i][i]) )
        
            int divisor = matrix1[i][i];
            for ( j = i; j < n; ++ j )//归一化矩阵
            
                matrix1[i][j] /= divisor;
                matrix2[i][j] /= divisor;
            
            for ( j = i + 1; j < n; ++ j )//高斯消元法处理行列式
            
                int multiple = matrix1[j][i];
                for ( int k = i; k < n; ++ k )
                
                    matrix1[i][j] -= matrix1[i][k] * multiple;
                    matrix2[i][j] -= matrix2[i][k] * multiple;
                
            
        
        else
            return false;
    
    return true;


void output( double ** matrix, const int n )

    for ( int i = 0; i < n; ++i )
    
        for ( int j = 0; j < n; ++ j )
            cout << matrix[i][j] << ' ';
        cout<<endl;
    


void destroy( double ** matrix, const int n )

    for ( int i = 0; i < n; ++ i )
        delete [] matrix[i];
    delete [] matrix;


int main()

    int n;
    double ** matrix1;
    double ** matrix2;
    while ( cin >> n )
    
        create( matrix1, n );
        create( matrix2, n );
        input( matrix1, n);
        if ( inverse(matrix1, matrix2, n) )
            output( matrix2, n );
        else
            cout << "No inverse matrix" << endl;
        destroy( matrix1, n );
        destroy( matrix2, n );
    
    return 0;

扩展1:

利用矩阵的初等行变换也可以判断一个矩阵是否可逆,即分块矩阵(A︱E)经过初等行变换,原来A的位置不能变换为单位阵E,那么A不可逆。


扩展2:

利用矩阵初等行变换解矩阵方程

对于一般的矩阵方程,我们可以先求自变量系数矩阵的逆,然后乘以结果矩阵即可得到自变量矩阵


ps:最后来点福利:

下面是几道OJ上的高斯消元法求解线性方程组的题目:

POJ 1222 EXTENDED LIGHTS OUT
http://acm.pku.edu.cn/JudgeOnline/problem?id=1222
POJ 1681 Painter's Problem
http://acm.pku.edu.cn/JudgeOnline/problem?id=1681
POJ 1753 Flip Game
http://acm.pku.edu.cn/JudgeOnline/problem?id=1753
POJ 1830 开关问题
http://acm.pku.edu.cn/JudgeOnline/problem?id=1830

POJ 3185 The Water Bowls

http://acm.pku.edu.cn/JudgeOnline/problem?id=3185
开关窗户,开关灯问题,很典型的求解线性方程组的问题。方程数和变量数均为行数*列数,直接套模板求解即可。但是,当出现无穷解时,需要枚举解的情况,因为无法判断哪种解是题目要求最优的。

POJ 2947 Widget Factory
http://acm.pku.edu.cn/JudgeOnline/problem?id=2947
求解同余方程组问题。与一般求解线性方程组的问题类似,只要在求解过程中加入取余即可。
注意:当方程组唯一解时,求解过程中要保证解在[3, 9]之间。

POJ 1166 The Clocks
http://acm.pku.edu.cn/JudgeOnline/problem?id=1166
经典的BFS问题,有各种解法,也可以用逆矩阵进行矩阵相乘。
但是这道题用高斯消元法解决好像有些问题(困扰了我N天...持续困扰中...),由于周期4不是素数,故在求解过程中不能进行取余(因为取余可能导致解集变大),但最后求解集时,还是需要进行取余操作,那么就不能保证最后求出的解是正确的...在discuss里提问了好几天也没人回答...希望哪位路过的大牛指点下~~

POJ 2065 SETI
http://acm.pku.edu.cn/JudgeOnline/problem?id=2065
同样是求解同余方程组问题,由于题目中的p是素数,可以直接在求解时取余,套用模板求解即可。(虽然AC的人很少,但它还是比较水的一道题,)

POJ 1487 Single-Player Games
http://acm.pku.edu.cn/JudgeOnline/problem?id=1487
很麻烦的一道题目...题目中的叙述貌似用到了编译原理中的词法定义(看了就给人不想做的感觉...)
解方程组的思想还是很好看出来了(前提是通读题目不下5遍...),但如果把树的字符串表达式转换成方程组是个难点,我是用栈 + 递归的做法分解的。首先用栈的思想求出该结点的孩子数,然后递归分别求解各个孩子。
这题解方程组也与众不同...首先是求解浮点数方程组,要注意精度问题,然后又询问不确定的变元,按前面说的方法求解。
一顿折腾后,这题居然写了6000+B...而且囧的是巨人C++ WA,G++ AC,可能还是精度的问题吧...看这题目,看这代码,就没有改的欲望...

hdu OJ 2449
http://acm.hdu.edu.cn/showproblem.php?pid=2449
哈尔滨现场赛的一道纯高斯题,当时鹤牛敲了1个多小时...主要就是写一个分数类,套个高精模板(偷懒点就Java...)搞定~~
注意下0和负数时的输出即可。

fze OJ 1704
http://acm.fzu.edu.cn/problem.php?pid=1704
福大月赛的一道题目,还是经典的开关问题,但是方程数和变元数不同(考验模板的时候到了~~),最后要求增广阵的阶,要用到高精度~~

Sgu 275 To xor or not to xor
http://acm.sgu.ru/problem.php?contest=0&problem=275
题解:
http://hi.baidu.com/czyuan%5Facm/blog/item/be3403d32549633d970a16ee.html


pps:对矩阵内涵的思考

如果不熟悉线性代数的概念,要去学习自然科学,现在看来就和文盲差不多。”,然而“按照现行的国际标准,线性代数是通过公理化来表述的,它是第二代数学模型,这就带来了教学上的困难。”
* 矩阵究竟是什么东西?向量可以被认为是具
有n个相互独立的性质(维度)的对象的表示,矩阵又是什么呢?我们如果认为矩阵是一组列(行)向量组成的新的复合向量的展开式,那么为什么这种展开式具有如此广泛的应用?特别是,为什么偏偏二维的展开式如此有用?如果矩阵中每一个元素又是一个向量,那么我们再展开一次,变成三维的立方阵,是不是更有用?
* 矩阵的乘法规则究竟为什么这样规定?为什么这样一种怪异的乘法规则却能够在实践中发挥如此巨大的功效?很多看上去似乎是完全不相关的问题,最后竟然都归结到矩阵的乘法,这难道不是很奇妙的事情?难道在矩阵乘法那看上去莫名其妙的规则下面,包含着世界的某些本质规律?如果是的话,这些本质规律是什么?
* 行列式究竟是一个什么东西?为什么会有如此怪异的计算规则?行列式与其对应方阵本质上是什么关系?为什么只有方阵才有对应的行列式,而一般矩阵就没有(不要觉得这个问题很蠢,如果必要,针对m x n矩阵定义行列式不是做不到的,之所以不做,是因为没有这个必要,但是为什么没有这个必要)?而且,行列式的计算规则,看上去跟矩阵的任何计算规则都没有直观的联系,为什么又在很多方面决定了矩阵的性质?难道这一切仅是巧合?
* 矩阵为什么可以分块计算?分块计算这件事情看上去是那么随意,为什么竟是可行的?
* 对于矩阵转置运算AT,有(AB)T = BTAT,对于矩阵求逆运算A-1,有(AB)-1 = B-1A-1。两个看上去完全没有什么关系的运算,为什么有着类似的性质?这仅仅是巧合吗?
* 为什么说P-1AP得到的矩阵与A矩阵“相似”?这里的“相似”是什么意思?
* 特征值和特征向量的本质是什么?它们定义就让人很惊讶,因为Ax =λx,一个诺大的矩阵的效应,竟然不过相当于一个小小的数λ,确实有点奇妙。但何至于用“特征”甚至“本征”来界定?它们刻划的究竟是什么?
今天先谈谈对线形空间和矩阵的几个核心概念的理解。首先说说空间(space),这个概念是现代数学的命根子之一,从拓扑空间开始,一步步往上加定义,可以形成很多空间。线形空间其实还是比较初级的,如果在里面定义了范数,就成了赋范线性空间。赋范线性空间满足完备性,就成了巴那赫空间;赋范线性空间中定义角度,就有了内积空间,内积空间再满足完备性,就得到希尔伯特空间。
总之,空间有很多种。你要是去看某种空间的数学定义,大致都是“存在一个集合,在这个集合上定义某某概念,然后满足某些性质”,就可以被称为空间。这未免有点奇怪,为什么要用“空间”来称呼一些这样的集合呢?大家将会看到,其实这是很有道理的。
我们一般人最熟悉的空间,毫无疑问就是我们生活在其中的(按照牛顿的绝对时空观)的三维空间,从数学上说,这是一个三维的欧几里德空间,我们先不管那么多,先看看我们熟悉的这样一个空间有些什么最基本的特点。仔细想想我们就会知道,这个三维的空间:1. 由很多(实际上是无穷多个)位置点组成;2. 这些点之间存在相对的关系;3. 可以在空间中定义长度、角度;4. 这个空间可以容纳运动,这里我们所说的运动是从一个点到另一个点的移动(变换),而不是微积分意义上的“连续”性的运动,
事实上,不管是什么空间,都必须容纳和支持在其中发生的符合规则的运动(变换)。你会发现,在某种空间中往往会存在一种相对应的变换,比如拓扑空间中有拓扑变换,线性空间中有线性变换,仿射空间中有仿射变换,其实这些变换都只不过是对应空间中允许的运动形式而已。
因此只要知道,“空间”是容纳运动的一个对象集合,而变换则规定了对应空间的运动。
下面我们来看看线性空间。线性空间的定义任何一本书上都有,但是既然我们承认线性空间是个空间,那么有两个最基本的问题必须首先得到解决,那就是:
1. 空间是一个对象集合,线性空间也是空间,所以也是一个对象集合。那么线性空间是什么样的对象的集合?或者说,线性空间中的对象有什么共同点吗?
2. 线性空间中的运动如何表述的?也就是,线性变换是如何表示的?
我们先来回答第一个问题,回答这个问题的时候其实是不用拐弯抹角的,可以直截了当的给出答案。线性空间中的任何一个对象,通过选取基和坐标的办法,都可以表达为向量的形式。通常的向量空间我就不说了,举两个不那么平凡的例子:
L1. 最高次项不大于n次的多项式的全体构成一个线性空间,也就是说,这个线性空间中的每一个对象是一个多项式。如果我们以x0, x1, ..., xn为基,那么任何一个这样的多项式都可以表达为一组n+1维向量,其中的每一个分量ai其实就是多项式中x(i-1)项的系数。值得说明的是,基的选取有多种办法,只要所选取的那一组基线性无关就可以。这要用到后面提到的概念了,所以这里先不说,提一下而已。
L2. 闭区间[a, b]上的n阶连续可微函数的全体,构成一个线性空间。也就是说,这个线性空间的每一个对象是一个连续函数。对于其中任何一个连续函数,根据魏尔斯特拉斯定理,一定可以找到最高次项不大于n的多项式函数,使之与该连续函数的差为0,也就是说,完全相等。这样就把问题归结为L1了。后面就不用再重复了。
所以说,向量是很厉害的,只要你找到合适的基,用向量可以表示线性空间里任何一个对象。这里头大有文章,因为向量表面上只是一列数,但是其实由于它的有序性,所以除了这些数本身携带的信息之外,还可以在每个数的对应位置上携带信息。为什么在程序设计中数组最简单,却又威力无穷呢?根本原因就在于此。这是另一个问题了,这里就不说了。
下面来回答第二个问题,这个问题的回答会涉及到线性代数的一个最根本的问题。
线性空间中的运动,被称为线性变换。也就是说,你从线性空间中的一个点运动到任意的另外一个点,都可以通过一个线性变化来完成。那么,线性变换如何表示呢?很有意思,在线性空间中,当你选定一组基之后,不仅可以用一个向量来描述空间中的任何一个对象,而且可以用矩阵来描述该空间中的任何一个运动(变换)。而使某个对象发生对应运动的方法,就是用代表那个运动的矩阵,乘以代表那个对象的向量。
简而言之,在线性空间中选定基之后,向量刻画对象,矩阵刻画对象的运动,用矩阵与向量的乘法施加运动。
是的,矩阵的本质是运动的描述。如果以后有人问你矩阵是什么,那么你就可以响亮地告诉他,矩阵的本质是运动的描述
可是多么有意思啊,向量本身不是也可以看成是n x 1矩阵吗?这实在是很奇妙,一个空间中的对象和运动竟然可以用相类同的方式表示。能说这是巧合吗?如果是巧合的话,那可真是幸运的巧合!可以说,线性代数中大多数奇妙的性质,均与这个巧合有直接的关系。
   “矩阵是运动的描述”,到现在为止,好像大家都还没什么意见。但是我相信早晚会有数学系出身的网友来拍板转。因为运动这个概念,在数学和物理里是跟微积分联系在一起的。我们学习微积分的时候,总会有人照本宣科地告诉你,初等数学是研究常量的数学,是研究静态的数学,高等数学是变量的数学,是研究运动的数学。大家口口相传,差不多人人都知道这句话。但是真知道这句话说的是什么意思的人,好像也不多。简而言之,在我们人类的经验里,运动是一个连续过程,从A点到B点,就算走得最快的光,也是需要一个时间来逐点地经过AB之间的路径,这就带来了连续性的概念。而连续这个事情,如果不定义极限的概念,根本就解释不了。古希腊人的数学非常强,但就是缺乏极限观念,所以解释不了运动,被芝诺的那些著名悖论(飞箭不动、飞毛腿阿喀琉斯跑不过乌龟等四个悖论)搞得死去活来。因为这篇文章不是讲微积分的,所以我就不多说了。有兴趣的读者可以去看看齐民友教授写的《重温微积分》。我就是读了这本书开头的部分,才明白“高等数学是研究运动的数学”这句话的道理。
不过在我这个《理解矩阵》的文章里,“运动”的概念不是微积分中的连续性的运动,而是瞬间发生的变化。比如这个时刻在A点,经过一个“运动”,一下子就“跃迁”到了B点,其中不需要经过A点与B点之间的任何一个点。这样的“运动”,或者说“跃迁”,是违反我们日常的经验的。不过了解一点量子物理常识的人,就会立刻指出,量子(例如电子)在不同的能量级轨道上跳跃,就是瞬间发生的,具有这样一种跃迁行为。所以说,自然界中并不是没有这种运动现象,只不过宏观上我们观察不到。但是不管怎么说,“运动”这个词用在这里,还是容易产生歧义的,说得更确切些,应该是“跃迁”。因此这句话可以改成:
“矩阵是线性空间里跃迁的描述”。
可是这样说又太物理,也就是说太具体,而不够数学,也就是说不够抽象。因此我们最后换用一个正牌的数学术语——变换,来描述这个事情。这样一说,大家就应该明白了,所谓变换,其实就是空间里从一个点(元素/对象)到另一个点(元素/对象)的跃迁。比如说,拓扑变换,就是在拓扑空间里从一个点到另一个点的跃迁。再比如说,仿射变换,就是在仿射空间里从一个点到另一个点的跃迁。附带说一下,这个仿射空间跟向量空间是亲兄弟。做计算机图形学的朋友都知道,尽管描述一个三维对象只需要三维向量,但所有的计算机图形学变换矩阵都是4 x 4的。说其原因,很多书上都写着“为了使用中方便”,这在我看来简直就是企图蒙混过关。真正的原因,是因为在计算机图形学里应用的图形变换,实际上是在仿射空间而不是向量空间中进行的。想想看,在向量空间里相一个向量平行移动以后仍是相同的那个向量,而现实世界等长的两个平行线段当然不能被认为同一个东西,所以计算机图形学的生存空间实际上是仿射空间。而仿射变换的矩阵表示根本就是4 x 4的。又扯远了,有兴趣的读者可以去看《计算机图形学——几何工具算法详解》。
一旦我们理解了“变换”这个概念,矩阵的定义就变成:
“矩阵是线性空间里的变换的描述。”
到这里为止,我们终于得到了一个看上去比较数学的定义。不过还要多说几句。教材上一般是这么说的,在一个线性空间V里的一个线性变换T,当选定一组基之后,就可以表示为矩阵。因此我们还要说清楚到底什么是线性变换,什么是基,什么叫选定一组基。线性变换的定义是很简单的,设有一种变换T,使得对于线性空间V中间任何两个不相同的对象x和y,以及任意实数a和b,有:
T(ax + by) = aT(x) + bT(y),
那么就称T为线性变换。
定义都是这么写的,但是光看定义还得不到直觉的理解。线性变换究竟是一种什么样的变换?我们刚才说了,变换是从空间的一个点跃迁到另一个点,而线性变换,就是从一个线性空间V的某一个点跃迁到另一个线性空间W的另一个点的运动。这句话里蕴含着一层意思,就是说一个点不仅可以变换到同一个线性空间中的另一个点,而且可以变换到另一个线性空间中的另一个点去。不管你怎么变,只要变换前后都是线性空间中的对象,这个变换就一定是线性变换,也就一定可以用一个非奇异矩阵来描述。而你用一个非奇异矩阵去描述的一个变换,一定是一个线性变换。有的人可能要问,这里为什么要强调非奇异矩阵?所谓非奇异,只对方阵有意义,那么非方阵的情况怎么样?这个说起来就会比较冗长了,最后要把线性变换作为一种映射,并且讨论其映射性质,以及线性变换的核与像等概念才能彻底讲清楚。我觉得这个不算是重点,如果确实有时间的话,以后写一点。以下我们只探讨最常用、最有用的一种变换,就是在同一个线性空间之内的线性变换。也就是说,下面所说的矩阵,不作说明的话,就是方阵,而且是非奇异方阵。学习一门学问,最重要的是把握主干内容,迅速建立对于这门学问的整体概念,不必一开始就考虑所有的细枝末节和特殊情况,自乱阵脚。
接着往下说,什么是基呢?这个问题在后面还要大讲一番,这里只要把基看成是线性空间里的坐标系就可以了。注意是坐标系,不是坐标值,这两者可是一个“对立矛盾统一体”。这样一来,“选定一组基”就是说在线性空间里选定一个坐标系。就这意思。
好,最后我们把矩阵的定义完善如下:
“矩阵是线性空间中的线性变换的一个描述。在一个线性空间中,只要我们选定一组基,那么对于任何一个线性变换,都能够用一个确定的矩阵来加以描述。”
理解这句话的关键,在于把“线性变换”与“线性变换的一个描述”区别开。一个是那个对象,一个是对那个对象的表述。就好像我们熟悉的面向对象编程中,一个对象可以有多个引用,每个引用可以叫不同的名字,但都是指的同一个对象。如果还不形象,那就干脆来个很俗的类比。
比如有一头猪,你打算给它拍照片,只要你给照相机选定了一个镜头位置,那么就可以给这头猪拍一张照片。这个照片可以看成是这头猪的一个描述,但只是一个片面的的描述,因为换一个镜头位置给这头猪拍照,能得到一张不同的照片,也是这头猪的另一个片面的描述。所有这样照出来的照片都是这同一头猪的描述,但是又都不是这头猪本身。
同样的,对于一个线性变换,只要你选定一组基,那么就可以找到一个矩阵来描述这个线性变换。换一组基,就得到一个不同的矩阵。所有这些矩阵都是这同一个线性变换的描述,但又都不是线性变换本身。
但是这样的话,问题就来了如果你给我两张猪的照片,我怎么知道这两张照片上的是同一头猪呢?同样的,你给我两个矩阵,我怎么知道这两个矩阵是描述的同一个线性变换呢?如果是同一个线性变换的不同的矩阵描述,那就是本家兄弟了,见面不认识,岂不成了笑话。
好在,我们可以找到同一个线性变换的矩阵兄弟们的一个性质,那就是:
若矩阵A与B是同一个线性变换的两个不同的描述(之所以会不同,是因为选定了不同的基,也就是选定了不同的坐标系),则一定能找到一个非奇异矩阵P,使得A、B之间满足这样的关系:
A = P-1BP
线性代数稍微熟一点的读者一下就看出来,这就是相似矩阵的定义。没错,所谓相似矩阵,就是同一个线性变换的不同的描述矩阵。按照这个定义,同一头猪的不同角度的照片也可以成为相似照片。俗了一点,不过能让人明白。
而在上面式子里那个矩阵P,其实就是A矩阵所基于的基与B矩阵所基于的基这两组基之间的一个变换关系。关于这个结论,可以用一种非常直觉的方法来证明(而不是一般教科书上那种形式上的证明),如果有时间的话,我以后在blog里补充这个证明。
这个发现太重要了。原来一族相似矩阵都是同一个线性变换的描述啊!难怪这么重要!工科研究生课程中有矩阵论、矩阵分析等课程,其中讲了各种各样的相似变换,比如什么相似标准型,对角化之类的内容,都要求变换以后得到的那个矩阵与先前的那个矩阵式相似的,为什么这么要求?因为只有这样要求,才能保证变换前后的两个矩阵是描述同一个线性变换的。当然,同一个线性变换的不同矩阵描述,从实际运算性质来看并不是不分好环的。有些描述矩阵就比其他的矩阵性质好得多。这很容易理解,同一头猪的照片也有美丑之分嘛。所以矩阵的相似变换可以把一个比较丑的矩阵变成一个比较美的矩阵,而保证这两个矩阵都是描述了同一个线性变换。
这样一来,矩阵作为线性变换描述的一面,基本上说清楚了。但是,事情没有那么简单,或者说,线性代数还有比这更奇妙的性质,那就是,矩阵不仅可以作为线性变换的描述,而且可以作为一组基的描述。而作为变换的矩阵,不但可以把线性空间中的一个点给变换到另一个点去,而且也能够把线性空间中的一个坐标系(基)表换到另一个坐标系(基)去。而且,变换点与变换坐标系,具有异曲同工的效果。线性代数里最有趣的奥妙,就蕴含在其中。理解了这些内容,线性代数里很多定理和规则会变得更加清晰、直觉。
首先来总结一下前面两部分的一些主要结论:
1. 首先有空间,空间可以容纳对象运动的。一种空间对应一类对象。
2. 有一种空间叫线性空间,线性空间是容纳向量对象运动的。
3. 运动是瞬时的,因此也被称为变换。
4. 矩阵是线性空间中运动(变换)的描述。
5. 矩阵与向量相乘,就是实施运动(变换)的过程。
6. 同一个变换,在不同的坐标系下表现为不同的矩阵,但是它们的本质是一样的,所以本征值相同。
   下面让我们把视力集中到一点以改变我们以往看待矩阵的方式。我们知道,线性空间里的基本对象是向量,而向量是这么表示的:
        [a1, a2, a3, ..., an]
 矩阵呢?矩阵是这么表示的:
        a11, a12, a13, ..., a1n
        a21, a22, a23, ..., a2n
                  ...
        an1, an2, an3, ..., ann
        不用太聪明,我们就能看出来,矩阵是一组向量组成的。特别的,n维线性空间里的方阵是由n个n维向量组成的。我们在这里只讨论这个n阶的、非奇异的方阵,如果一组向量是彼此线性无关的话,那么它们就可以成为度量这个线性空间的一组基,从而事实上成为一个坐标系体系,其中每一个向量都躺在一根坐标轴上,并且成为那根坐标轴上的基本度量单位(长度1)。现在到了关键的一步。看上去矩阵就是由一组向量组成的,而且如果矩阵非奇异的话(我说了,只考虑这种情况),那么组成这个矩阵的那一组向量也就是线性无关的了,也就可以成为度量线性空间的一个坐标系。结论:矩阵描述了一个坐标系。之所以矩阵又是运动,又是坐标系,那是因为——“运动等价于坐标系变换”。对不起,这话其实不准确,我只是想让你印象深刻。准确的说法是:“对象的变换等价于坐标系的变换”。或者:“固定坐标系下一个对象的变换等价于固定对象所处的坐标系变换。” 说白了就是: “运动是相对的。”        
    让我们想想,达成同一个变换的结果,比如把点(1, 1)变到点(2, 3)去,你可以有两种做法。第一,坐标系不动,点动,把(1, 1)点挪到(2, 3)去。第二,点不动,变坐标系,让x轴的度量(单位向量)变成原来的1/2,让y轴的度量(单位向量)变成原先的1/3,这样点还是那个点,可是点的坐标就变成(2, 3)了。方式不同,结果一样。从第一个方式来看,那就是我在《理解矩阵》1/2中说的,把矩阵看成是运动描述,矩阵与向量相乘就是使向量(点)运动的过程。在这个方式下,
      Ma = b的意思是:
      “向量a经过矩阵M所描述的变换,变成了向量b。”而从第二个方式来看,矩阵M描述了一个坐标系,姑且也称之为M。那么:
      Ma = b的意思是:
      “有一个向量,它在坐标系M的度量下得到的度量结果向量为a,那么它在坐标系I的度量下,这个向量的度量结果是b。”
      这里的I是指单位矩阵,就是主对角线是1,其他为零的矩阵。而这两个方式本质上是等价的。我希望你务必理解这一点,因为这是本篇的关键。正因为是关键,所以我得再解释一下。在M为坐标系的意义下,如果把M放在一个向量a的前面,形成Ma的样

以上是关于高斯消元法的主要内容,如果未能解决你的问题,请参考以下文章

高斯消元法

高斯消元法

高斯消元基础知识介绍

高斯消元法(Gauss Elimination)超详解&模板

线代之高斯消元

列主元高斯消元法求解线性方程组