本文我们讨论一类特殊的线性方程组, 即三对角方程组
其中, 是已知系数,
是未知变量,
三对角方程组是一类特殊的线性方程组, 其系数矩阵具有三对角的性质, 即除了主对角线上的元素外, 只有上对角线和下对角线上的非零元素. 这种形式的方程组在科学和工程领域中经常出现, 因此求解三对角方程组具有重要的应用价值.
求解三对角方程组的方法有多种, 其中一种常用的方法是追赶法(也称为托马斯算法). 追赶法通过一系列前向和后向的替代操作, 将三对角方程组转化为一个更容易求解的上三角或下三角方程组, 然后使用回代或前代方法求解. 这个方法通常具有较高的数值稳定性和效率. 以下是追赶法的主要步骤:
- 前向消元
- 追赶法首先通过前向消元来将原始三对角方程组转化为上三角形式. 在这一步, 通过逐行操作, 将非零元素下移到主对角线下, 同时更新右侧常数项.
- 这一步骤通常从第一行开始, 通过一系列线性组合将上对角线上的元素归零. 然后, 将结果用于更新下一行, 依此类推, 直到将原方程组转化为上三角形式.
- 后向代入(或回代) 一旦方程组被转化为上三角形式, 就可以使用回代或后向代入的方法来求解方程组. 这是一个从最后一行开始的过程, 逐步计算出每个未知变量的值, 通过从下到上的线性组合.
算法可表示为伪代码如下:
其C++实现如下:
#include <armadillo>
using namespace arma;
#define THROW_DIV_0 throw "方阵为奇异矩阵!";
#define THROW_DIV_0_DEL \
{ \
free(B); \
THROW_DIV_0 \
}
// #define THROW_DIV_0_ALL {free(A);free(B);free(C);THROW_DIV_0}
#define JUDGE_0(x) \
if (!(x)) \
THROW_DIV_0
#define JUDGE_0_DEL(x) \
if (x) \
THROW_DIV_0_DEL
// #define JUDGE_0_ALL(x) if(x)THROW_DIV_0_ALL
// 追赶过程, 要求b[0]不为零, n>1
// 结果输出在d中
bool chase(unsigned n, const double *a, double *b, const double *c, double *d)
{
unsigned k(n);
while (--k)
{
if (!*b)
return true;
double m(*a++ / *b), &pre_d(*d++);
*++b -= m * *c++;
*d -= m * pre_d;
}
if (!*b)
return true;
*d /= *b;
while (--n)
{
double &pre_d(*d--);
(*d -= *--c * pre_d) /= *--b;
}
return false;
}
// b[0]为零的追赶, n>2
bool chase_0(unsigned n, const double *a, double *b, const double *c, double *d)
{
if (!*c || !*a)
return true;
const double t(*d / *c++);
*++d -= t * *(++b)++;
*++d -= t * *++a;
unsigned k(n);
while (--k)
{
if (!*b)
return true;
const double m(*++a / *b), &pre_d(*d++);
*++b -= m * *++c;
*d -= m * pre_d;
}
if (!*b)
return true;
*d /= *b;
a -= n;
while (--n)
{
const double &pre_x(*d--);
(*d -= *c-- * pre_x) /= *--b;
}
const double &x3(*d);
double &x2(*--d);
*--d = (x2 - *c * x3) / *a;
x2 = t;
return false;
}
/*
* 追赶法
* R:解向量
* a:左下方的副对角线
* b:主对角线
* c:右上方的副对角线
* d:常数项
*/
void Thomas_algorithm(vec &R, const vec &a, const vec &b, const vec &c, const vec &d)
{
if (b.n_elem != d.n_elem)
throw "系数矩阵阶数与常数向量维数不相等!";
if (a.n_elem != b.n_elem - 1 || a.n_elem != c.n_elem)
throw "主、副对角线维数不匹配!";
if (a.empty())
{
double t(d.at(0) / b.at(0));
JUDGE_0(t)
R = {t};
return;
}
unsigned n(b.n_elem);
R.set_size(n);
const double *Bp(&b.at(--n)), *Dp(&d.at(n));
double *B((double *)malloc(sizeof(double) * b.n_elem)), *bp(B + n), *dp(&R.at(n));
*bp = *Bp, *dp = *Dp;
do
*--bp = *--Bp, *--dp = *--Dp;
while (--n);
if (*bp) // 第1个元素不为0, 可以直接进入追赶过程
{
JUDGE_0_DEL(chase(b.n_elem, &a.at(0), B, &c.at(0), dp))
free(B);
return;
}
if (!--(n = a.n_elem)) // 系数矩阵只有2阶
{
const double &C(c.at(0)), &A(a.at(0));
double &ele_2(*(dp + 1));
JUDGE_0_DEL(!C || !A)
*dp = (Dp[1] - B[1] * (ele_2 = *Dp / C)) / A;
free(B);
return;
}
// const unsigned A_size(sizeof(double)*n);
// const double *AP(&a.at(0)),*CP(&c.at(0));
// JUDGE_0_DEL(!*CP)
// double *A((double*)malloc(A_size)),*C((double*)malloc(A_size)),last_sol(*Dp/ *CP);
JUDGE_0_DEL(chase_0(n, &a.at(0), B, &c.at(0), dp))
// free(A);
free(B);
// free(C);
}
例 求解线性方程组
代入程序求得解向量为.