数值计算——线性方程组的解法
矩阵分解
矩阵分解是将矩阵拆解为数个矩阵的乘积,可分为三角分解、满秩分解、QR分解、Jordan分解和SVD(奇异值)分解等,常见的有三种: 1)三角分解法 (LU):将原正方矩阵分解成一个上三角形矩阵或是排列的上三角形矩阵和一个 下三角形矩阵,用途主要在简化一个大矩阵的行列式值的计算过程,求 反矩阵,和求解联立方程组。 2)QR 分解法 (QR) 3)奇异值分解法 (SVD)。
LU分解法的实现
将系数矩阵A转变成等价两个矩阵L和U的乘积 ,其中L和U分别是下三角和上三角矩阵。 LU分解在本质上是高斯消元法的一种表达形式。实质上是将A通过初等行变换变成一个上三角矩阵,其变换矩阵就是一个单位下三角矩阵。这正是所谓的杜尔里特算法:从下至上地对矩阵A做初等行变换,将对角线左下方的元素变成零,然后再证明这些行变换的效果等同于左乘一系列单位下三角矩阵,这一系列单位下三角矩阵的乘积的逆就是L矩阵,它也是一个单位下三角矩阵。
下面是实现算法,其中α和β分别代表L和U中的元素
实现方法: 1)使用matlab:MATLAB以lu函数来执行lu分解法, 其语法为[L,U]=lu(A)。 2)使用java:
package com.kexin.lab2;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
/**
* LU分解函数进行矩阵分解
*
* @author KeXin
*
*/
public class LU {
/**
* 分解矩阵A = LU
*
* @param a
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static List myLU(double[][] a) {
double[][] U = a; // a是要分解的矩阵
double[][] L = new double[a.length][a.length];
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a.length; j++) {
L[i][j] = 1;
}
}
for (int j = 0; j < a[0].length - 1; j++) {
if (a[j][j] == 0) {
break;
}
for (int i = j + 1; i < a.length; i++) {
double mult = a[i][j] / a[j][j];
for (int k = j; k < a[i].length; k++) {
U[i][k] = a[i][k] - a[j][k] * mult;
// 得出上三角矩阵U,通过减去矩阵的第一行,第二行,第一行(第二行)得到上三角矩阵
}
L[i][j] = mult; // 得到下三角矩阵是得出上三角矩阵的乘积因子
}
}
List aa = new ArrayList<>();
aa.add(L);
aa.add(U);
return aa;
}
/**
* 回代求解上三角矩阵函数
*
* @param u
* @param b
* @return
*/
public static double[] UpT(double[][] u, double[] b) {
int len = u.length;
double[] x = new double[len];
for (int i = len - 1; i >= 0; i--) {
if (u[i][i] == 0)
break;
x[i] = b[i] / u[i][i];
for (int k = 0; k <= i - 1; k++) {
b[k] = b[k] - u[k][i] * x[i];
}
}
return x;
}
/**
* 回代求解下三角矩阵函数
*
* @param l
* @param b
* @return
*/
public static double[] LowT(double[][] l, double[] b) {
int len = l.length;
double[] x = new double[len];
for (int i = 0; i < len; i++) {
if (l[i][i] == 0)
break;
x[i] = b[i] / l[i][i];
for (int k = 0; k <= i - 1; k++) {
b[k] = b[k] - l[k][i] * x[i];
}
}
return x;
}
/**
* Ax=b 转化成LUx=b, 先求Ly=b,再求Ux=y
*
* @param l
* @param u
* @param b
* @return
*/
public static double[] myLU(double[][] l, double[][] u, double[] b) {
double[] y = LowT(l, b);
double[] x = UpT(u, y);
return x;
}
@SuppressWarnings("rawtypes")
public static void main(String[] args) {
// 初始化矩阵
double[][] aa = { { 0, 85, 56, 54 }, { 76, 63, 7, 20 }, { 21, 67, 88, 73 }, { 19.3, 43, 30.2, 29.4 } };
List a = myLU(aa);
double[][] L = (double[][]) a.get(0);
double[][] U = (double[][]) a.get(1);
int len = L.length;
// 打印矩阵L、U
System.out.println("LU矩阵分解");
System.out.println("------L is: -------");
for (int i = 0; i < len; i++) {
for (int j = 0; j < i; j++) {
System.out.print(L[i][j] + "\t");
}
System.out.println("1");
}
System.out.println("------U is: -------\n");
for (int i = 0; i < len; i++) {
for (int v = 0; v < i; v++) {
System.out.print("\t");
}
for (int j = i; j < len; j++) {
System.out.print(U[i][j] + "\t");
}
System.out.println();
}
// 使用LU求解Ax=b
double[] b = { 218, 109, 141, 93.7 };
double[] result = myLU(L, U, b);
System.out.println("------X is: -------\n");
for (int i = 0; i < result.length; i++) {
System.out.print(result[i] + "\t");
}
}
}
Cholesky 分解
Cholesky分解法又称平方根法,是当A为实对称正定矩阵时,LU三角分解法的变形,即把一个对称正定的矩阵表示成一个下三角矩阵L和其转置的乘积。它要求矩阵的所有特征值必须大于零,故分解的下三角的对角元也是大于零的。
使用java实现
package com.kexin.lab2;
public class Cholesky {
/**
* 生成Hilbert矩阵
*
* @param n
* @return
*/
public static double[][] Hilbert(int n) {
double[][] a = new double[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
a[i][j] = (double) 1 / (i + j + 1);
}
}
return a;
}
/**
* Cholesky分解
*
* @param a
* @return
*/
public static double[][] Cholesky(double[][] a) {
int n = a.length;
// 楚列斯基分解
for (int k = 0; k < n; k++) {
a[k][k] = Math.sqrt(a[k][k]);
for (int i = k + 1; i < n; i++) {
a[i][k] = a[i][k] / a[k][k];
}
for (int j = k + 1; j < n; j++) {
for (int i = k + 1; i < n; i++) {
a[i][j] = a[i][j] - a[i][k] * a[j][k];
}
}
}
return a;
}
/**
* @param args
*/
public static void main(String[] args) {
// 生成Hilbert矩阵n=5\6\7\8
int n = 5;
double[][] a = Hilbert(n);
double aa[][] = a;
double x[] = new double[n];
double b[] = new double[n];
double bb[] = new double[n];
double y[] = new double[n];
double r[] = new double[n];
double f[] = new double[n];
// 初始化b
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
b[i] = b[i] + a[i][j];
}
}
// 初始化bb
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
bb[i] = bb[i] + aa[i][j];
}
}
// 楚列斯基分解
a = Cholesky(a);
// 前代计算
for (int j = 0; j < n; j++) {
if (a[j][j] != 0) {
x[j] = b[j] / a[j][j];
b[j] = x[j];
}
for (int i = j + 1; i < n; i++) {
b[i] = b[i] - a[i][j] * x[j];
}
}
// 将a进行转置
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
a[i][j] = a[j][i];
}
}
// 回代计算
for (int j = n - 1; j >= 0; j--) {
if (a[j][j] != 0) {
x[j] = b[j] / a[j][j];
}
for (int i = 0; i <= j - 1; i++) {
b[i] = b[i] - a[i][j] * x[j];
}
}
// 输出结果
System.out.println("------结果为:------");
for (int i = 0; i < n; i++) {
System.out.println(x[i]);
}
// 计算残差
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
y[i] = y[i] + aa[i][j] * x[j];
}
r[i] = bb[i] - y[i];
}
// 输出残差
System.out.println("------残差为:-----");
for (int i = 0; i < n; i++) {
System.out.println(r[i]);
}
// 误差的无穷范数
for (int i = 0; i < n; i++) {
y[i] = x[i] - 1;
}
if (y[0] < 0)
y[0] = -y[0];
double fanshu = y[0];
for (int i = 1; i < n; i++) {
if (y[i] < 0)
y[i] = -y[i];
if (y[i] > fanshu)
fanshu = y[i];
}
// 输出误差的无穷范数
System.out.println("-------误差的无穷范数为------");
System.out.println(fanshu);
// 计算条件数
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
f[j] = f[j] + aa[j][i];
}
}
}
}