迭代法求解线性方程组(C++)

尽管所有的线性方程组都可以通过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(k1),,x(0)),k=0,1,

x ( k + 1 ) \bm x^{(k+1)} x(k+1)只与 x ( k ) \bm x^{(k)} x(k)有关, 且 F k \bm 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)=Bkx(k)+fk

其中, B k ∈ R n × n \bm B_k\in\mathbb R^{n\times n} BkRn×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

称为单步定常线性迭代法. 本文主要讨论这种类型的迭代法.

求解算法

Jacobi迭代法

设有线性方程组

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} D1, 得

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=D1(L+U)x+D1b

若我们取 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:=D1(L+U),fJ:=D1b, 则有如下迭代公式

x ( k + 1 ) = B J x + f J \bm x^{(k+1)}=\bm B_J\bm x+\bm f_J x(k+1)=BJx+fJ

上式被称为Jacobi迭代法. 因此, 我们可以将算法表示为如下伪代码:

Gauss-Seidel迭代法

若我们把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)=aii1(bij=1i1aijxj(k)j=i+1naijxj(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),,xi1(k+1)已经算出, 可以将 x 1 ( k ) , ⋯   , x i − 1 ( k ) x_1^{(k)},\cdots,x_{i-1}^{(k)} x1(k),,xi1(k)替换为 x 1 ( k + 1 ) , ⋯   , x i − 1 ( k + 1 ) x_1^{(k+1)},\cdots,x_{i-1}^{(k+1)} x1(k+1),,xi1(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)=BGSx+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迭代法

矩阵法

/*
 * 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();
}

Gauss-Seidel迭代法

矩阵法

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();
}

实例分析

例1

用迭代法求解线性方程组

( 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} 884179229 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迭代较慢.

例2

用迭代法求解线性方程组

{ 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)
00.0000000.0000000.000000
11.4285711.0000000.666667
21.0952380.6547620.126984
31.2987530.8313490.277778
41.2304420.7682110.193311
51.2635950.7978670.222521
61.2510130.7864200.208564
71.2566360.7914820.213904
81.2543870.7894450.211529
91.2553570.7903190.212482
101.2549600.7899600.212072
111.2551280.7901120.212240
121.2550580.7900490.212169
131.2550880.7900760.212198
141.2550750.7900640.212186
151.2550810.7900690.212191
161.2550780.7900670.212189
171.2550790.7900680.212190
181.2550790.7900680.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)
00.0000000.0000000.000000
11.4285710.8214290.166667
21.2636050.8003830.208003
31.2548020.7911490.212011
41.2549760.7901250.212200
51.2550680.7900670.212192
61.2550780.7900670.212190
71.2550790.7900680.212190

由此可见, Gauss-Seidel迭代法在本例中收敛速度较快.


  1. 这里用了Qt的QFile, 也可以方便地改写为使用标准库的FILE或文件输出流. ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zsc_118

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值