尽管所有的线性方程组都可以通过Gauss消去法和LU分解法求解, 但是对于大型的稀疏线性代数方程组, 往往求解效率较低. 这时候常使用的求解算法是迭代法, 其基本形式为:
x ( k + 1 ) = F k ( x ( k ) , x ( k ? 1 ) , ? ? , x ( 0 ) ) , k = 0 , 1 , ? \bm x^{(k+1)}=\bm F_k(\bm x^{(k)},\bm x^{(k-1)},\cdots,\bm x^{(0)}),\quad k=0,1,\cdots x(k+1)=Fk?(x(k),x(k?1),?,x(0)),k=0,1,?
若 x ( k + 1 ) \bm x^{(k+1)} x(k+1)只与 x ( k ) \bm x^{(k)} x(k)有关, 且 F k F_k Fk?是线性的, 即
x ( k + 1 ) = B k x ( k ) + f k \bm x^{(k+1)}=\bm B_k\bm x^{(k)}+\bm f_k x(k+1)=Bk?x(k)+fk?
其中, B k ∈ R n × n \bm B_k\in\mathbb R^{n\times n} Bk?∈Rn×n, 称为单步线性迭代法, B k \bm B_k Bk?称为迭代矩阵. 若 B k \bm B_k Bk?与 f k \bm f_k fk?都与 k k k无关, 即
x ( k + 1 ) = B x ( k ) + f \bm x^{(k+1)}=\bm B\bm x^{(k)}+\bm f x(k+1)=Bx(k)+f
称为单步定常线性迭代法. 本文主要讨论这种类型的迭代法.
设有线性方程组
A x = b \bm A\bm x=\bm b Ax=b
其中, A = ( a i j ) \bm A=(a_{ij}) A=(aij?)非奇异, 我们将 A \bm A A的对角线 D \bm D D, 上三角部分 U \bm U U, 下三角部分 L \bm L L分离, 即
A = L + D + U \bm A=\bm L+\bm D+\bm U A=L+D+U
若 D \bm D D非奇异, 我们可以将 L x \bm L\bm x Lx和 U x \bm U\bm x Ux移到方程的右端, 再将方程两端同时左乘 D ? 1 \bm D^{-1} D?1, 得
x = ? D ? 1 ( L + U ) x + D ? 1 b \bm x=-\bm D^{-1}(\bm L+\bm U)\bm x+\bm D^{-1}\bm b x=?D?1(L+U)x+D?1b
若我们取 B J : = ? D ? 1 ( L + U ) , f J : = D ? 1 b \bm B_J:=-\bm D^{-1}(\bm L+\bm U),\bm f_J:=\bm D^{-1}\bm b BJ?:=?D?1(L+U),fJ?:=D?1b, 则有如下迭代公式
x ( k + 1 ) = B J x + f J \bm x^{(k+1)}=\bm B_J\bm x+\bm f_J x(k+1)=BJ?x+fJ?
上式被称为Jacobi迭代法. 因此, 我们可以将算法表示为如下伪代码:
若我们把Jacobi迭代法还原为方程组的形式, 我们有
x i ( k + 1 ) = 1 a i i ( b i ? ∑ j = 1 i ? 1 a i j x j ( k ) ? ∑ j = i + 1 n a i j x j ( k ) ) , i = 1 , 2 , ? ? , n x_i^{(k+1)}=\frac1{a_{ii}}(b_i-\sum_{j=1}^{i-1}a_{ij}x_j^{(k)}-\sum_{j=i+1}^{n}a_{ij}x_j^{(k)}),\quad i=1,2,\cdots,n xi(k+1)?=aii?1?(bi??j=1∑i?1?aij?xj(k)??j=i+1∑n?aij?xj(k)?),i=1,2,?,n
观察上式可知, 在实际计算 x i ( k + 1 ) x_i^{(k+1)} xi(k+1)?的时候, 分量 x 1 ( k + 1 ) , ? ? , x i ? 1 ( k + 1 ) x_1^{(k+1)},\cdots,x_{i-1}^{(k+1)} x1(k+1)?,?,xi?1(k+1)?已经算出, 可以将 x 1 ( k ) , ? ? , x i ? 1 ( k ) x_1^{(k)},\cdots,x_{i-1}^{(k)} x1(k)?,?,xi?1(k)?替换为 x 1 ( k + 1 ) , ? ? , x i ? 1 ( k + 1 ) x_1^{(k+1)},\cdots,x_{i-1}^{(k+1)} x1(k+1)?,?,xi?1(k+1)?, 这样每次迭代都可以用到当前最新的计算结果, 这就是Gauss-Seidel迭代法. 相应地, Gauss-Seidel迭代法也有矩阵形式:
x ( k + 1 ) = B G S x + f G S \bm x^{(k+1)}=\bm B_{GS}\bm x+\bm f_{GS} x(k+1)=BGS?x+fGS?
其中, B G S : = ? ( D + L ) ? 1 U , f G S : = ( D + L ) ? 1 b \bm B_{GS}:=-(\bm D+\bm L)^{-1}\bm U,\bm f_{GS}:=(\bm D+\bm L)^{-1}\bm b BGS?:=?(D+L)?1U,fGS?:=(D+L)?1b. 同样地, Gauss-Seidel迭代法可表示为伪代码如下:
将Gauss-Seidel迭代法和Jacobi迭代法作加权平均, 我们就得到了超松弛迭代法(又叫SOR法). 其中Gauss-Seidel迭代法的权重 ω \omega ω称为松弛因子.
同以上两种迭代法, 超松弛迭代法也可以表示为如下的矩阵形式
x ( k + 1 ) = B ω x + f ω \bm x^{(k+1)}=\bm B_\omega\bm x+\bm f_\omega x(k+1)=Bω?x+fω?
其中, B ω : = ( D + ω L ) ? 1 ( ( 1 ? ω ) D ? ω U ) , f ω : = ω ( D + ω L ) ? 1 b \bm B_\omega:=(\bm D+\omega\bm L)^{-1}((1-\omega)\bm D-\omega\bm U),\bm f_\omega:=\omega(\bm D+\omega\bm L)^{-1}\bm b Bω?:=(D+ωL)?1((1?ω)D?ωU),fω?:=ω(D+ωL)?1b. 同样地, 超松弛迭代法可表示为伪代码如下:
首先进行预处理如下:
#include <armadillo>
#include <vector>
#include <QFile>
#include <QString>
#include <string>
using namespace arma;
using namespace std;
为了方便写入向量, 编写一个向量导出函数.1
void write_vec(QFile &file, const vec &x)
{
string s;
for (auto &i : x)
s += (to_string(i)) += ',';
*(s.end() - 1) = '\n';
file.write(s.c_str());
}
/*
* Jacobi迭代法(矩阵法)
* A:系数矩阵
* b:常数向量
* x:迭代初始向量
* u:迭代过程
* e:迭代精度
*/
void Jacobi_mat(const mat &A, const vec &b, vec &x, vector<vec> &u, const double &e = 1e-6)
{
vec x1(x), b1(b.n_elem, 1, fill::none);
u.push_back(x);
mat A1(A.n_rows, A.n_cols, fill::none);
for (unsigned i(0); i != x.n_rows; ++i)
{
if (abs(A.at(i, i)) < e)
throw "对角线元素为零!";
A1.at(i, i) = 0;
x.at(i) = b1.at(i) = b.at(i) / A.at(i, i);
for (unsigned j(0); j != A.n_cols; ++j)
if (i != j)
x.at(i) -= (A1.at(i, j) = A.at(i, j) / A.at(i, i)) * x1.at(j);
}
u.push_back(x);
while (norm(x1 - x) > e)
{
x1 = x;
u.push_back(x = b1 - A1 * x);
}
}
/*
* Jacobi迭代法(分量法)
* A:系数矩阵
* b:常数向量
* x:迭代初始向量
* u:迭代过程
* e:迭代精度
*/
void Jacobi_ele(const mat &A, const vec &b, vec &x, vector<vec> &u, const double &e = 1e-6)
{
vec x1(x), b1(b.n_elem, 1, fill::none);
u.push_back(x);
mat A1(A.n_rows, A.n_cols, fill::none);
for (unsigned i(0); i != x.n_rows; ++i)
{
if (abs(A.at(i, i)) < e)
throw "对角线元素为零!";
x.at(i) = b1.at(i) = b.at(i) / A.at(i, i);
for (unsigned j(0); j != A.n_cols; ++j)
if (i != j)
x.at(i) -= (A1.at(i, j) = A.at(i, j) / A.at(i, i)) * x1.at(j);
}
u.push_back(x);
while (norm(x1 - x) > e)
{
x1 = x;
for (unsigned i(0); i != x.n_rows; ++i)
{
x.at(i) = b1.at(i);
for (unsigned j(0); j != A.n_cols; ++j)
if (i != j)
x.at(i) -= A1.at(i, j) * x1.at(j);
}
u.push_back(x);
}
}
/*
* Jacobi迭代法(分量法)
* A:系数矩阵
* b:常数向量
* x:迭代初始向量
* p:迭代过程保存路径
* e:迭代精度
*/
void Jacobi(const mat &A, const vec &b, vec &x, const QString &p, const double &e = 1e-6)
{
QFile file(p);
if (!file.open(QIODevice::WriteOnly))
throw "文件保存失败!";
vec x1(x), b1(b.n_elem, 1, fill::none);
write_vec(file, x);
mat A1(A.n_rows, A.n_cols, fill::none);
for (unsigned i(0); i != x.n_rows; ++i)
{
if (abs(A.at(i, i)) < e)
throw "对角线元素为零!";
x.at(i) = b1.at(i) = b.at(i) / A.at(i, i);
for (unsigned j(0); j != A.n_cols; ++j)
if (i != j)
x.at(i) -= (A1.at(i, j) = A.at(i, j) / A.at(i, i)) * x1.at(j);
}
write_vec(file, x);
while (norm(x1 - x) > e)
{
x1 = x;
for (unsigned i(0); i != x.n_rows; ++i)
{
x.at(i) = b1.at(i);
for (unsigned j(0); j != A.n_cols; ++j)
if (i != j)
x.at(i) -= A1.at(i, j) * x1.at(j);
}
write_vec(file, x);
}
file.close();
}
void Gauss_Seidel_mat(const mat &A, const vec &b, vec &x, vector<vec> &u, const double &e = 1e-6)
{
mat T(inv(trimatl(A))), A1(T * trimatu(A, 1));
vec b1(T * b), x1;
u.clear();
u.push_back(x);
do
{
x1 = x;
u.push_back(x = b1 - A1 * x);
} while (norm(x - x1) > e);
}
/*
* Gauss-Seidel迭代法(分量法)
* A:系数矩阵
* b:常数向量
* x:迭代初始向量
* u:迭代过程
* e:迭代精度
*/
void Gauss_Seidel_ele(const mat &A, const vec &b, vec &x, vector<vec> &u, const double &e = 1e-6)
{
vec x1(x), b1(b.n_elem, 1, fill::none);
u.push_back(x);
mat A1(A.n_rows, A.n_cols, fill::none);
for (unsigned i(0); i != x.n_rows; ++i)
{
if (abs(A.at(i, i)) < e)
throw "对角线元素为零!";
x.at(i) = b1.at(i) = b.at(i) / A.at(i, i);
unsigned j(-1);
while (++j != i)
x.at(i) -= (A1.at(i, j) = A.at(i, j) / A.at(i, i)) * x.at(j);
while (++j != A.n_cols)
x.at(i) -= (A1.at(i, j) = A.at(i, j) / A.at(i, i)) * x.at(j);
}
u.push_back(x);
while (norm(x1 - x) > e)
{
x1 = x;
for (unsigned i(0); i != x.n_rows; ++i)
{
x.at(i) = b1.at(i);
unsigned j(-1);
while (++j != i)
x.at(i) -= A1.at(i, j) * x.at(j);
while (++j != A.n_cols)
x.at(i) -= A1.at(i, j) * x.at(j);
}
u.push_back(x);
}
}
/*
* Gauss-Seidel迭代法(分量法)
* A:系数矩阵
* b:常数向量
* x:迭代初始向量
* p:迭代过程保存路径
* e:迭代精度
*/
void Gauss_Seidel(const mat &A, const vec &b, vec &x, const QString &p, const double &e = 1e-6)
{
QFile file(p);
if (!file.open(QIODevice::WriteOnly))
throw "文件保存失败!";
vec x1(x), b1(b.n_elem, 1, fill::none);
write_vec(file, x);
mat A1(A.n_rows, A.n_cols, fill::none);
for (unsigned i(0); i != x.n_rows; ++i)
{
if (abs(A.at(i, i)) < e)
throw "对角线元素为零!";
x.at(i) = b1.at(i) = b.at(i) / A.at(i, i);
unsigned j(-1);
while (++j != i)
x.at(i) -= (A1.at(i, j) = A.at(i, j) / A.at(i, i)) * x.at(j);
while (++j != A.n_cols)
x.at(i) -= (A1.at(i, j) = A.at(i, j) / A.at(i, i)) * x.at(j);
}
write_vec(file, x);
while (norm(x1 - x) > e)
{
x1 = x;
for (unsigned i(0); i != x.n_rows; ++i)
{
x.at(i) = b1.at(i);
unsigned j(-1);
while (++j != i)
x.at(i) -= A1.at(i, j) * x.at(j);
while (++j != A.n_cols)
x.at(i) -= A1.at(i, j) * x.at(j);
}
write_vec(file, x);
}
file.close();
}
/*
* SOR法(矩阵法)
* A:系数矩阵
* b:常数向量
* w:松弛因子
* x:迭代初始向量
* u:迭代过程
* e:迭代精度
*/
void SOR_mat(const mat &A, const vec &b, const double &w, vec &x, vector<vec> &u, const double &e = 1e-6)
{
mat L(trimatl(A, -1)), D(diagmat(A)), U(trimatu(A, 1)), T(inv(trimatl(w * L + D))), A1(T * ((1 - w) * D - w * U));
vec b1(T * b * w), x1;
u.clear();
u.push_back(x);
do
{
x1 = x;
u.push_back(x = b1 + A1 * x);
} while (norm(x - x1) > e);
}
/*
* SOR法(分量法)
* A:系数矩阵
* b:常数向量
* w:松弛因子
* x:迭代初始向量
* u:迭代过程
* e:迭代精度
*/
void SOR_ele(const mat &A, const vec &b, const double &w, vec &x, vector<vec> &u, const double &e = 1e-6)
{
double w1(1 - w);
vec x1(x), b1(b.n_elem, 1, fill::none);
u.push_back(x);
mat A1(A.n_rows, A.n_cols, fill::none);
for (unsigned i(0); i != x.n_rows; ++i)
{
if (abs(A.at(i, i)) < e)
throw "对角线元素为零!";
(x.at(i) *= w1) += b1.at(i) = w * b.at(i) / A.at(i, i);
unsigned j(-1);
while (++j != i)
x.at(i) -= (A1.at(i, j) = w * A.at(i, j) / A.at(i, i)) * x.at(j);
while (++j != A.n_cols)
x.at(i) -= (A1.at(i, j) = w * A.at(i, j) / A.at(i, i)) * x.at(j);
}
u.push_back(x);
while (norm(x1 - x) > e)
{
x1 = x;
for (unsigned i(0); i != x.n_rows; ++i)
{
(x.at(i) *= w1) += b1.at(i);
unsigned j(-1);
while (++j != i)
x.at(i) -= A1.at(i, j) * x.at(j);
while (++j != A.n_cols)
x.at(i) -= A1.at(i, j) * x.at(j);
}
u.push_back(x);
}
}
/*
* SOR法(分量法)
* A:系数矩阵
* b:常数向量
* w:松弛因子
* x:迭代初始向量
* p:迭代过程保存路径
* e:迭代精度
*/
void SOR(const mat &A, const vec &b, const double &w, vec &x, const QString &p, const double &e = 1e-6)
{
QFile file(p);
if (!file.open(QIODevice::WriteOnly))
throw "文件保存失败!";
double w1(1 - w);
vec x1(x), b1(b.n_elem, 1, fill::none);
write_vec(file, x);
mat A1(A.n_rows, A.n_cols, fill::none);
for (unsigned i(0); i != x.n_rows; ++i)
{
if (abs(A.at(i, i)) < e)
throw "对角线元素为零!";
(x.at(i) *= w1) += b1.at(i) = w * b.at(i) / A.at(i, i);
unsigned j(-1);
while (++j != i)
x.at(i) -= (A1.at(i, j) = w * A.at(i, j) / A.at(i, i)) * x.at(j);
while (++j != A.n_cols)
x.at(i) -= (A1.at(i, j) = w * A.at(i, j) / A.at(i, i)) * x.at(j);
}
write_vec(file, x);
while (norm(x1 - x) > e)
{
x1 = x;
for (unsigned i(0); i != x.n_rows; ++i)
{
(x.at(i) *= w1) += b1.at(i);
unsigned j(-1);
while (++j != i)
x.at(i) -= A1.at(i, j) * x.at(j);
while (++j != A.n_cols)
x.at(i) -= A1.at(i, j) * x.at(j);
}
write_vec(file, x);
}
file.close();
}
用迭代法求解线性方程组
( 8 1 2 8 7 2 4 9 9 ) x = ( 10 18 17 ) \begin{pmatrix} 8&1&2\\8&7&2\\4&9&9 \end{pmatrix}\bm x=\begin{pmatrix} 10\\18\\17 \end{pmatrix} ?884?179?229? ?x= ?101817? ?
选取初值 x 0 = ( 0 , 0 , 0 ) T \bm x_0=(0,0,0)^T x0?=(0,0,0)T, 代入程序并测量时间得解向量为 x = ( 1.0625 , 1.3333 , 0.0833 ) T \bm x=(1.0625,1.3333,0.0833)^T x=(1.0625,1.3333,0.0833)T, 运行时间见下表:
算法实现方式 | 耗时( μ \mu μs) |
---|---|
Jacobi迭代法(分量法) | 0.0393829 |
Jacobi迭代法(矩阵法) | 0.0456848 |
Gauss-Seidel迭代法(分量法) | 0.00231934 |
Gauss-Seidel迭代法(矩阵法) | 0.00382996 |
SOR法(分量法) | 0.00256348 |
SOR法(矩阵法) | 0.00396729 |
其中, 超松弛迭代法的松弛因子取1.2. 由表可知, 矩阵法实现比分量法实现较慢, 但可读性更高, 这是因为矩阵法实现涉及到矩阵求逆运算, 耗时较多; 在迭代方法方面, Gauss-Seidel迭代法和超松弛迭代法较快, 而Jacobi迭代较慢.
用迭代法求解线性方程组
{ 7 x 1 + x 2 + 2 x 3 = 10 x 1 + 8 x 2 + 2 x 3 = 8 2 x 1 + 2 x 2 + 9 x 3 = 6 \begin{cases} 7x_1+x_2+2x_3=10\\ x_1+8x_2+2x_3=8\\ 2x_1+2x_2+9x_3=6 \end{cases} ? ? ??7x1?+x2?+2x3?=10x1?+8x2?+2x3?=82x1?+2x2?+9x3?=6?
取初值为 ( 0 , 0 , 0 ) T (0,0,0)^T (0,0,0)T, 利用Jacobi迭代法有迭代过程如下表:
k k k | x 1 ( k ) x_1^{(k)} x1(k)? | x 2 ( k ) x_2^{(k)} x2(k)? | x 3 ( k ) x_3^{(k)} x3(k)? |
---|---|---|---|
0 | 0.000000 | 0.000000 | 0.000000 |
1 | 1.428571 | 1.000000 | 0.666667 |
2 | 1.095238 | 0.654762 | 0.126984 |
3 | 1.298753 | 0.831349 | 0.277778 |
4 | 1.230442 | 0.768211 | 0.193311 |
5 | 1.263595 | 0.797867 | 0.222521 |
6 | 1.251013 | 0.786420 | 0.208564 |
7 | 1.256636 | 0.791482 | 0.213904 |
8 | 1.254387 | 0.789445 | 0.211529 |
9 | 1.255357 | 0.790319 | 0.212482 |
10 | 1.254960 | 0.789960 | 0.212072 |
11 | 1.255128 | 0.790112 | 0.212240 |
12 | 1.255058 | 0.790049 | 0.212169 |
13 | 1.255088 | 0.790076 | 0.212198 |
14 | 1.255075 | 0.790064 | 0.212186 |
15 | 1.255081 | 0.790069 | 0.212191 |
16 | 1.255078 | 0.790067 | 0.212189 |
17 | 1.255079 | 0.790068 | 0.212190 |
18 | 1.255079 | 0.790068 | 0.212190 |
而利用Gauss-Seidel迭代法有迭代过程如下表:
k k k | x 1 ( k ) x_1^{(k)} x1(k)? | x 2 ( k ) x_2^{(k)} x2(k)? | x 3 ( k ) x_3^{(k)} x3(k)? |
---|---|---|---|
0 | 0.000000 | 0.000000 | 0.000000 |
1 | 1.428571 | 0.821429 | 0.166667 |
2 | 1.263605 | 0.800383 | 0.208003 |
3 | 1.254802 | 0.791149 | 0.212011 |
4 | 1.254976 | 0.790125 | 0.212200 |
5 | 1.255068 | 0.790067 | 0.212192 |
6 | 1.255078 | 0.790067 | 0.212190 |
7 | 1.255079 | 0.790068 | 0.212190 |
由此可见, Gauss-Seidel迭代法在本例中收敛速度较快.
这里用了Qt的QFile
, 也可以方便地改写为使用标准库的FILE
或文件输出流. ??