數值分析:線性方程組的直接解法(上)

SXWisON發表於2024-11-27

提綱

  1. 背景介紹
  2. 三角方程組
  3. Gauss消去法
  4. 附錄

一、背景介紹

1.1 線性方程組的相關概念

線性方程組在解決現實實際問題中直接產生,最小二乘資料擬合、微分方程邊值問題和初邊值問題的數值解產生了大量的線性方程組。
線性方程組係數矩陣的型別分別有

  1. 稠密型(dense):幾乎所有元素都是非零的
  2. 稀疏型(sparse):有大量零元素
  3. 帶狀的(banded)
  4. 三角狀(triangular)
  5. 塊狀的(block structure)

解線性方程組的方法可以分為兩類

  1. 直接法(direct method)
    經過有限步四則運算可球的方程組準確解的方法
  2. 迭代法(iterative method)
    從一個近似值出發,構造某種演算法,使其逐步接近準確解

大多科學計算應用經過建模和數值離散後,都可歸結為如下兩種形式方程組的求解:
方程組形式

\[\begin{cases} a_{11}x_1+a_{12}x_2+\cdots+a_{1n}x_n=b_1,\\ a_{21}x_1+a_{22}x_2+\cdots+a_{2n}x_n=b_2,\\ \cdots\\ a_{n1}x_1+a_{n2}x_2+\cdots+a_{nn}x_n=b_n \end{cases} \]

矩陣形式

\[\begin{bmatrix} a_{11} & a_{12} &\cdots &a_{1n}\\ a_{21} & a_{22} &\cdots &a_{2n}\\ &&\cdots&\\ a_{n1} & a_{n2} &\cdots &a_{nn}\\ \end{bmatrix} \begin{bmatrix} x_1\\x_2\\\vdots\\x_n \end{bmatrix}= \begin{bmatrix} b_1\\b_2\\\vdots\\b_n \end{bmatrix} \]

\(Ax=b\)有唯一解\(\iff A\)非奇異

C++中的線性方程組


線上性代數中,一矩陣的尺寸通常稱為階數(order)或維度(dimension)。以下示例程式碼在主函式中定義了稀疏矩陣\(A\),常向量\(b\)和解向量\(x\)

Eigen庫中,可以採用Eigen::MatrixXd表示矩陣型別,採用Eigen::VectorXd表示向量型別。矩陣和向量的尺寸可以在建立時進行設定。

需要注意的是,Eigen庫中Eigen::VectorXd預設為列向量,如果需要將其作為行向量進行運算,需要在使用時進行轉置,例如:X.transpose()

即使沒有硬性的要求,但還是建議讀者使用const size_t型別的變數單獨儲存矩陣的尺寸,這將使得程式碼維護變得更容易。

#include <iostream>
#include <Eigen/Dense>

int main() {
    // 矩陣的階數
    const size_t order = 6;

    // 定義係數矩陣 A
    Eigen::MatrixXd A(order, order); // 指定尺寸為 order * order
    // 定義常向量 b
    Eigen::VectorXd b(order);  // 指定尺寸為 order * 1 
    // 定義解向量 x
    Eigen::VectorXd x(order); // 指定尺寸為 order * 1
}

採用直接法求解線性方程組的求解器通常包含三個輸入,即:係數矩陣\(A\)、常向量\(b\)和解向量\(x\)
在進行求解前,首先應當檢查輸入是否符合求解器要求,例如針對上三角矩陣的求解器需要檢查係數矩陣是否為上三角矩陣;一般的,輸入應滿足三個要求:

  1. 係數矩陣\(A\)為方陣
  2. 係數矩陣\(A\)的行數等於常向量\(b\)的行數
  3. 係數矩陣\(A\)的列數等於解向量\(x\)的行數

矩陣的行數可以透過.rows()方法得到
矩陣的列數可以透過.cols()方法得到

該方法對於向量同樣適用,特別的,Eigen庫中向量的列數總是1

以下給出參考的實現:

void size_check(const Eigen::MatrixXd& A,
    const Eigen::VectorXd& b,
    const Eigen::VectorXd& x)
{
    // 檢查A是否為方陣
    if (A.rows() != A.cols()) {
        throw std::invalid_argument("Error: The coefficient matrix of the system of equations is not a square matrix.");
    }
    // 檢查係數矩陣A的尺寸是否與常向量b的尺寸匹配
    if (A.rows() != b.rows()) {
        throw std::invalid_argument("Error: The order of the coefficient matrix A does not match the order of the constant vector b.");
    }
    // 檢查係數矩陣A的尺寸是否與解向量x的尺寸匹配
    if (A.cols() != x.rows()) {
        throw std::invalid_argument("Error: The order of the coefficient matrix A does not match the order of the solution vector x.");
    }
}

void solve(const Eigen::MatrixXd& A,
    const Eigen::VectorXd& b,
    Eigen::VectorXd& x) 
{
    // 檢查尺寸是否合適
    size_check(A, b, x);
    
    // 求解
    // ...
}

在實際實現時有幾個應注意的細節

為什麼不將解向量\(x\)作為輸出?
將解向量\(x\)作為輸出的函式的使用方式為:ans=solve(A,b),如果返回值的尺寸與變數ans的尺寸不一致則會導致程式錯誤。為了避免該問題,必須在建立變數ans時設定尺寸,並在求解前檢查尺寸,虛擬碼如下

Eigen::VetorXd x(order);

if (x.rows() == A.cols()) { // 尺寸檢查
    x = solve(A,b);
}

顯然,形如ans=solve(A,b,x)的求解器更為易用,其型別檢查可以在函式內部完成,這帶來了更好的封裝性、可維護性。

在必要的時候新增&const關鍵字
在傳遞函式引數時,&關鍵字表明瞭該傳參方式為引用傳參,區別於普通傳參,引用傳參方式使得函式無需在其內部複製一個副本,而是可以直接在原變數上進行操作。無需複製副本顯著降低了程式的效能開銷。
對於普通傳參,const關鍵字表明內部複製的副本為常變數。對於引用傳參,const關鍵字表明該函式不具有修改該變數的許可權,只具備讀取(訪問)的許可權。

三角方程組

下三角方程組


\[\begin{bmatrix} a_{11} &&&\\ a_{21} & a_{22} &&\\ \vdots&&\ddots&\\ a_{n1} & a_{n2} &\cdots &a_{nn}\\ \end{bmatrix} \begin{bmatrix} x_1\\x_2\\\vdots\\x_n \end{bmatrix}= \begin{bmatrix} b_1\\b_2\\\vdots\\b_n \end{bmatrix} \]

解法:前代法(Forward substitution)

\[\begin{cases} x_1 = b_1/a_{11}\\ x_2 = (b_2-a_{21}x_1)/a_{22}\\ \cdots\\ x_i = (b_i-\sum_{j=1}^{i-1}a_{ij}x_j)/a_{ii}, i=1,2,\cdots,n \end{cases} \]

下三角矩陣判斷
Eigen庫並沒有提供直接的判斷矩陣是否為下三角矩陣的方法,因此採用瞭如下的判斷方法:

  1. 首先提取矩陣的嚴格上三角部分(不包含對角線)
  2. 判斷其是否全部為零,如果嚴格上三角部分全部為零,那麼其為下三角矩陣

前代法求解

  1. 檢查輸入尺寸是否匹配
  2. 判斷係數矩陣是否為下三角矩陣
  3. 採用前代法求解。

\[\begin{align*} x_i = (b_i-\sum_{j=1}^{i-1}a_{ij}x_j)/a_{ii}, i=1,2,\cdots,n \tag{2.1} \end{align*} \]

外層迴圈用於遍歷解向量\(x\)的每個元素,從下標0開始,遍歷至下標n-1結束。迴圈內部分佈實現式\((2.1)\)的計算,對於求和部分,巢狀內層迴圈實現。

矩陣/向量元素訪問
在訪問矩陣/向量的元素時元素,採用括號運算子進行訪問。

#include "check.h"

bool isLowerTriangular(const Eigen::MatrixXd& A) {
    // 獲取矩陣的嚴格上三角部分(不包括對角線)
    Eigen::MatrixXd upperTriangularPart = A.triangularView<Eigen::StrictlyUpper>();

    // 檢查嚴格上三角部分是否全為零
    return upperTriangularPart.isZero();
}

void forward_substitution(const Eigen::MatrixXd& A,
    const Eigen::VectorXd& b,
    Eigen::VectorXd& x) 
{
    // 檢查尺寸是否匹配
    size_check(A, b, x);
    // 判斷係數矩陣是否為下三角矩陣
    if (!isLowerTriangular(A)) {
        throw std::invalid_argument("Error: The matrix is not lower triangular.");
    }

    for (size_t i = 0; i < A.rows(); ++i) {
        x(i) = b(i);
        for (size_t j = 0; j + 1 <= i; ++j) { // j < i - 1
            x(i) -= A(i, j) * x(j);
        }
        x(i) /= A(i, i);
    }
}

注意事項

應當注意C++中的陣列索引是從0開始的,Eigen庫也沿用了這一習慣。

在求和\(\sum_{j=1}^{i-1}a_{ij}x_j\)的實現中,很容易錯誤的使用j<=i-1作為迴圈的終止條件,這實際上有一個風險,當i=0的時候,i-1並不是-1,而是最大的size_t型別的數,這將導致終止條件錯誤,因此,應當用j+1<=i

上三角方程組


\[\begin{bmatrix} a_{11} & a_{12} &\cdots &a_{1n}\\ & a_{22} &\cdots &a_{2n}\\ &&\ddots&\vdots\\ &&&a_{nn}\\ \end{bmatrix} \begin{bmatrix} x_1\\x_2\\\vdots\\x_n \end{bmatrix}= \begin{bmatrix} b_1\\b_2\\\vdots\\b_n \end{bmatrix} \]

解法:回代法(Back substitution)

\[\begin{cases} x_n = b_n/a_{nn}\\ x_{n-1} = (b_{n-1}-a_{n-1,n}x_n)/a_{n-1,n-1}\\ \cdots\\ x_i = (b_i-\sum_{j=1}^{i-1}a_{ij}x_j)/a_{ii}, i=n,n-1,\cdots,1 \end{cases} \]

上三角矩陣判斷
Eigen庫並沒有提供直接的判斷矩陣是否為上三角矩陣的方法,因此採用瞭如下的判斷方法:

  1. 首先提取矩陣的嚴格下三角部分(不包含對角線)
  2. 判斷其是否全部為零,如果嚴格下三角部分全部為零,那麼其為上三角矩陣

回代法求解

  1. 檢查輸入尺寸是否匹配
  2. 判斷係數矩陣是否為上三角矩陣
  3. 採用回代法求解。

\[\begin{align*} x_i = (b_i-\sum_{j=1}^{i-1}a_{ij}x_j)/a_{ii}, i=n,n-1,\cdots,1 \tag{2.2} \end{align*} \]

外層迴圈用於遍歷解向量\(x\)的每個元素,從下標n-1開始,遍歷至下標0結束。迴圈內部分佈實現式\((2.2)\)的計算,對於求和部分,巢狀內層迴圈實現。

bool isUpperTriangular(const Eigen::MatrixXd& A) {
    // 獲取矩陣的嚴格下三角部分(不包括對角線)
    Eigen::MatrixXd lowerTriangularPart = A.triangularView<Eigen::StrictlyLower>();

    // 檢查嚴格下三角部分是否全為零
    return lowerTriangularPart.isZero();
}

void back_substitution(const Eigen::MatrixXd& A,
    const Eigen::VectorXd& b,
    Eigen::VectorXd& x)
{
    // 檢查尺寸是否匹配
    size_check(A, b, x);
    // 判斷係數矩陣是否為上三角矩陣
    if (!isUpperTriangular(A)) {
        throw std::invalid_argument("Error: The matrix is not upper triangular.");
    }

    size_t n = A.rows();
    for (size_t i = n - 1; i != size_t(-1); --i) { // i != -1
        x(i) = b(i);
        for (size_t j = i + 1; j <= n - 1; ++j) {
            x(i) -= A(i, j) * x(j);
        }
        x(i) /= A(i, i);
    }
}

注意事項

外層迴圈的遍歷是從下標n-1開始,遍歷至下標0結束;一般習慣性的寫法是,以i>=0作為截止條件,但應當注意,size_t型別是非負的,事實上,對於size_t型別的變數,當其值為0時再做-1,其值為size_t(-1),因此,可以採用i!=size_t(-1)作為截止條件

高斯消元法

一般高斯消元法


高斯消元法(Gaussian Elimination)是一種用於求解線性方程組的經典方法。它透過逐步消去未知數,將方程組化為上三角形式,然後透過回代法求解未知數。高斯消元法主要分為兩個步驟:前向消元和後向回代,本文中將以前向消元為例展開討論。

前向消元(Forward Elimination)
前向消元法是從第一列開始,透過一些列的行變換,逐漸將原矩陣變換為一個上三角矩陣。假定矩陣的尺寸為\(N*N\),那麼高斯消元法需要進行\(N-1\)次,在第\(i\)時執行如下操作:

  1. 選擇主元:選擇第\(i\)列的元素\(A_{i,i}\)作為主元
  2. 消去操作:透過將第\(i\)行的適當倍數加到其他行,使得當前列的其它元素變為零。

消去操作的公式如下:

\[\begin{cases} m_{ik}&={a_{ik}^{(k)}}/{a_{kk}^{(k)}}\\ a_{ij}^{(k+1)}&=a_{ij}^{(k)}-m\cdot a_{kj}^{(k)}\\ b_{i}^{(k+1)}&=b_{i}^{(k)}-m\cdot b_{k}^{(k)}\\ k=1,2,\dots,n-1\\ i,j=k+1.\dots,n \end{cases}\tag{3.1} \]

矩陣的第一步消元過程可以參考以下公式:

\[\left[ \begin{array}{cccc|c} a_{11}^{(1)} & a_{12}^{(1)} &\cdots &a_{1n}^{(1)}&b_1^{(1)}\\ a_{21}^{(1)} & a_{22}^{(1)} &\cdots &a_{2n}^{(1)}&b_2^{(1)}\\ &&\cdots&&\vdots\\ a_{n1}^{(1)} & a_{n2}^{(1)} &\cdots &a_{nn}^{(1)}&b_n^{(1)}\\ \end{array} \right] \longrightarrow \left[ \begin{array}{cccc|c} a_{11}^{(1)} & a_{12}^{(1)} &\cdots &a_{1n}^{(1)}&b_1^{(1)}\\ 0 & a_{22}^{(2)} &\cdots &a_{2n}^{(2)}&b_2^{(2)}\\ &&\cdots&&\vdots\\ 0 & a_{n2}^{(2)} &\cdots &a_{nn}^{(2)}&b_n^{(2)}\\ \end{array} \right] \]

在下述程式中,取樣行向量相減的方式實現高斯消元法,相較於逐個元素相減,程式碼更簡潔易懂,易維護。

void simple_gauss_elimination(Eigen::MatrixXd& A, Eigen::VectorXd& b) {
	// 檢查尺寸是否匹配
	size_check(A, b);
	
	size_t n = A.rows();
	// 逐步消元為上三角矩陣
	for (size_t k = 0; k < n - 1; ++k) {
		// 提取矩陣的第k行
		Eigen::VectorXd temp = A.row(k);
		// 將第i列索引大於i的元素消為0
		for (size_t i = k + 1; i < n; ++i) {
			// 計算比值
			double m = A(i, k) / A(k, k);
			// 消元
			A.row(i) -= m * temp;
			b(i) -= m * b(k);
		}
	}
}

改進的高斯消元法


\(a^{(k)}_{kk}\to 0\),則\(m=a_{ik}^{(k)}/a_{kk}^{(0)}\to\infty\),此時直接用高斯消元法求解線性方程組是會由於舍入誤差的擴大,而導致解失真。

因此在原高斯消元法的基礎上,可以做改進,新增主元的選擇過程,該方法稱為列主元法,具體流程如下:

  1. 尋找第\(k\)列中第\(k\)行到第\(n\)行最大的元素,記為\(a_{jk}\)

\[\text{pivot}=\max_{k\leq i\leq n}\big|A(i,k)\big| \]

  1. 將第\(j\)行與第\(k\)行交換
  2. 進行高斯消元法
void gauss_elimination(Eigen::MatrixXd& A, Eigen::VectorXd& b) {
	// 檢查尺寸是否匹配
	size_check(A, b);

	size_t n = A.rows();
	// 逐步消元為上三角矩陣
	for (size_t k = 0; k < n; ++k) {
		// 選擇主元
		size_t j = k;
		double max = abs(A(j, k)); 
		for (size_t i = k + 1; i < n; ++i) {
			double d = abs(A(i, k));
			if (d > max) { // 選擇絕對值最大的元素
				j = i; max = d;
			}
		}
		// 交換主元
		if (j != k) {
			Eigen::VectorXd temp = A.row(j);
			A.row(j) = A.row(k);
			A.row(k) = temp;
			double temp_b = b(j);
			b(j) = b(k);
			b(k) = temp_b;
		}
		// 將第i列索引大於i的元素消為0
		for (size_t i = k + 1; i < n; ++i) {
			// 計算比值
			double m = A(i, k) / A(k, k);
			// 消元
			A.row(i) -= m * A.row(k);
			b(i) -= m * b(k);
		}
	}
}

注意事項

對方程\(Ax=b\)的係數矩陣\(A\)和常向量\(b\)同時做行變換時,方程的解\(x\)不變。

基於高斯消元法的一般線性方程求解


對於一般的線性方程組,可以先用高斯消元法將係數矩陣轉化為上三角矩陣,再透過回代法求解。

void gauss_solve(Eigen::MatrixXd A,
    Eigen::VectorXd b,
    Eigen::VectorXd& x)
{
    // 檢查尺寸是否匹配
    size_check(A, b, x);
    // 高斯消元法轉為上三角矩陣
    gauss_elimination(A, b);
    // 透過回代法求解
    back_substitution(A, b, x);
}

注意事項

切忌捨本逐末,雖然新增引用修飾符可以一定程度上提升效能,但是這會導致稀疏矩陣\(A\)和常向量\(b\)被修改,而使用者往往容易忽略這一點,因此為了保證安全性,此處不使用引用傳參

截止到目前,對係數矩陣\(A\)為下三角形矩陣的線性方程組有兩種求解方法,一種是採用前代法,一種是採用高斯消元結合回代法,在附錄中我們對同一組資料採用兩種方法分別計算結果,進行交叉驗證

附錄

功能測試方法


構建函式(方法)的測試程式流程如下:

  1. 從函式(方法)的名稱中提取縮寫,作為名聲空間的字首
  2. 定義測試函式,命名為test(),如果需要可以設計多個,例如:test1(), test2()
  3. 實現測試函式,一般來說,有以下步驟:①生成資料,②呼叫方法,③列印資料以及結果
  4. 在主函式中,呼叫該名聲空間下的測試函式test(),一般需要使用try-catch結構

示例程式碼如下:

namespace SMP{
    void test() {
        std::cout << "Hello World!";
    }
}

int main() {
    try{
        SMP::test();
    }
    catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}

在後續的附錄內容中,將省略main函式的設計,讀者只需按照上述方法呼叫即可。

前代法測試


namespace FWD{
    // test for forward_substitution()
    void test() { // 矩陣的階數
        const size_t order = 5;

        // 定義係數矩陣 A
        Eigen::MatrixXd A(order, order);
        // 定義常向量 b
        Eigen::VectorXd b(order);
        // 定義解向量 x
        Eigen::VectorXd x(order);

        // 設定矩陣為隨機數
        A.setRandom();
        b.setRandom();

        // 處理為方便手算的數字
        A = (1.5 + A.array()) * 2;;
        b *= 10;
        A = A.array().round().matrix();
        b = b.array().round().matrix();

        // 將嚴格上三角部分設定為零,使其成為下三角矩陣
        A.triangularView<Eigen::StrictlyUpper>().setZero();

        // 前代法
        forward_substitution(A, b, x);

        // 輸出結果
        std::cout << "A=\n" << A << "\n";
        std::cout << "b=\n" << b << "\n";
        std::cout << "x=\n" << x << "\n";
    }
}

效果展示
程式的輸出如下圖所示(經過拼接),經檢驗,該計算結果正確(讀者感興趣的可以手算一下試試)。
description

回代法測試


namespace BCK{
    // test for back_substitution()
    void test() { // 矩陣的階數
        const size_t order = 5;

        // 定義係數矩陣 A
        Eigen::MatrixXd A(order, order);
        // 定義常向量 b
        Eigen::VectorXd b(order);
        // 定義解向量 x
        Eigen::VectorXd x(order);

        // 設定矩陣為隨機數
        A.setRandom();
        b.setRandom();

        // 處理為方便手算的數字
        A = (1.5 + A.array()) * 2;;
        b *= 10;
        A = A.array().round().matrix();
        b = b.array().round().matrix();

        // 將嚴格下三角部分設定為零,使其成為上三角矩陣
        A.triangularView<Eigen::StrictlyLower>().setZero();

        // 回代法
        back_substitution(A, b, x);

        // 輸出結果
        std::cout << "A=\n" << A << "\n";
        std::cout << "b=\n" << b << "\n";
        std::cout << "x=\n" << x << "\n";
    }
}

效果展示
程式的輸出如下圖所示(經過拼接),經檢驗,該計算結果正確.
description

一般高斯消元法測試


namespace S_GSE {
    // test for simple_gauss_elimination
    void test() { // 矩陣的階數
        const size_t order = 5;

        // 定義係數矩陣 A
        Eigen::MatrixXd A(order, order);
        // 定義常向量 b
        Eigen::VectorXd b(order);

        // 設定矩陣為隨機數
        A.setRandom();
        b.setRandom();

        // 調整顯示精度為小數點後兩位
        std::cout << std::fixed << std::setprecision(2);

        // 輸出消元前矩陣
        std::cout << "A=\n" << A << "\n";
        std::cout << "b=\n" << b << "\n";

        // 前代法
        simple_gauss_elimination(A, b);

        // 輸出消元后矩陣
        std::cout << "A=\n" << A << "\n";
        std::cout << "b=\n" << b << "\n";
    }
}

效果展示
程式的輸出如下圖所示(經過拼接),顯示精度為小數點後兩位;經檢驗,該計算結果正確.
description

列主元法改進的高斯消元法測試


namespace GSE {
    // test for simple_gauss_elimination
    void test() { // 矩陣的階數
        const size_t order = 5;

        // 定義係數矩陣 A
        Eigen::MatrixXd A(order, order);
        // 定義常向量 b
        Eigen::VectorXd b(order);

        // 設定矩陣為隨機數
        A.setRandom();
        b.setRandom();

        // 調整顯示精度為小數點後兩位
        std::cout << std::fixed << std::setprecision(2);

        // 輸出消元前矩陣
        std::cout << "A=\n" << A << "\n";
        std::cout << "b=\n" << b << "\n";

        // 前代法
        gauss_elimination(A, b);

        // 輸出消元后矩陣
        std::cout << "A=\n" << A << "\n";
        std::cout << "b=\n" << b << "\n";
    }
}

程式的輸出如下圖所示(經過拼接),顯示精度為小數點後兩位;經檢驗,該計算結果正確.
description

高斯+回代法求解


namespace GS_SOLVE{
    void test1() {
        const size_t order = 5;

        Eigen::MatrixXd A(order, order);
        Eigen::VectorXd b(order);
        Eigen::VectorXd x(order);

        // 設定矩陣為隨機數
        A.setRandom();
        b.setRandom(); b = (1.0 + b.array()) * 5;

        // 前代法
        gauss_solve(A, b, x);

        // 輸出結果
        std::cout << std::fixed << std::setprecision(2);
        std::cout << "A=\n" << A << "\n";
        std::cout << "b=\n" << b << "\n";
        std::cout << "x=\n" << x << "\n";
    }

    void test2() {
        const size_t order = 5;

        Eigen::MatrixXd A(order, order);
        Eigen::VectorXd b(order);
        Eigen::VectorXd x1(order);
        Eigen::VectorXd x2(order);

        // 設定矩陣為隨機數
        A.setRandom();
        b.setRandom(); b = (1.0 + b.array()) * 5;

        // 將上三角部分設定為零,使其成為下三角矩陣
        A.triangularView<Eigen::StrictlyUpper>().setZero();

        // 高斯
        gauss_solve(A, b, x1);
        // 前代法
        forward_substitution(A, b, x2);

        // 輸出結果
        std::cout << std::fixed << std::setprecision(2);
        std::cout << "GS_solve:\n" << "x1=\n" << x1 << "\n";
        std::cout << "back_stt:\n" << "x2=\n" << x2 << "\n";
    }
}

測試1
函式GS_SOLVE::test1()用於測試高斯求解是否能夠正常工作,該程式的輸出如下圖所示(經過拼接),顯示精度為小數點後兩位;經檢驗,該計算結果正確.
description

測試2
函式GS_SOLVE::test2()採用交叉驗證法,分別採用前代法,一種是採用高斯消元結合回代法求解係數矩陣\(A\)為下三角矩陣的線性方程組,並對比計算結果;經檢驗,結果各方面功能正常。
description

相關文章