1. 程式人生 > >數值分析(二):C++實現三對角線方程組的追趕法

數值分析(二):C++實現三對角線方程組的追趕法

這次來實現三對角線方程組的追趕法,追趕法的本質還是高斯消元法,而且是沒選主元的高斯消元法,只是因為Ax=b中係數矩陣A非常特殊,所以就可以採用相對特殊的方法來解方程組。同樣,按照常規的步驟,先分析什麼是追趕法,再給出追趕法的數學步驟,最後用C++實現這種演算法。
(一)追趕法的功能和步驟
明確好目的,正所謂磨刀不誤砍柴工,做一件事情事先規劃好,那重要性真的是不言而喻。在一些實際問題中,對角佔優的三對角線方程組很常見,如熱傳導方程,形式如下圖1:
圖1 三對角線方程組
應用追趕法能解這種三對角線方程組還有很嚴苛的條件,那就是對角佔優,主對角線元素的絕對值要最大,大到什麼程度呢?大到絕對值比旁邊兩條次對角線的值的絕對值之和還要大,這樣才能用追趕法來接三對角線方程組,苛刻的條件如下圖2:
圖2 追趕法的前提條件


所以,追趕法就是用來解這類線性方程組的。
追趕法的步驟:追趕法的本質就是沒有選主元的高斯消元法,當然係數矩陣***A***都這麼特殊了,再用原始的高斯消元法就實在是浪費空間和時間了,特殊的特性就應該用起來,就像做數學證明題時一樣,要把事物本身的性質儘可能都用上。實現追趕法最有趣的不是數學方法,而是程式設計實現,因為之前的係數矩陣***A***是低階的稠密矩陣,所以程式設計採用的資料結構就是二維的矩陣,或者說是二重指標,但是實現追趕法,沒必要用到二重指標,只要一維陣列就夠用了,因為就三對角線上有元素,所以一維陣列的大小是**n+2(n-1)***,即**3n-1***就夠了,然後最重要的就是將係數矩陣***A**
(i,j)***元素同一維陣列***B[]***的位置***k***的元素相對應,將這層關係理清楚了,那麼追趕法就很容易程式設計實現了,二者的對應關係如下圖3。
圖3 係數矩陣和一維陣列對應關係
所以,資料結構確定,且對應關係理清之後,就又是沒選主元的高斯消元法了。數學語言的步驟如下圖4。
圖4 追趕法的步驟(數學語言描述)

(二)程式碼實現
筆者分別將追趕法寫成了函式和類兩種方式,仔細介紹函式double trde(int n, double A, double b)***,函式就是一個處理器,或是黑匣子,或說是類似對映一樣的東西,輸入引數,計算處理後得到我們想要的輸出,這裡引數就是係數矩陣的階數***n***,以及儲存有係數矩陣元素的一維陣列***A***(當然這裡以指標的形式傳輸),以及常數向量***b***,輸出就是得到解向量,同樣是指標形式。程式碼如下:

double* trde(int n, double* A, double* b)
{
	int k, j, m; m = 3 * n - 2;
	double s;
	for (k = 0; k < n - 1; k++)
	{
		j = 3 * k; s = A[j];
		if (fabs(3) + 1.0 == 1.0)
		{
			cout << "主對角元素是0,方程無解!!" << endl;
			return b;
		}
		A[j + 1] = A[j + 1] / s;//一下這四行是歸一化和消元,對角線下的元素沒必要進行操作
		b[k] = b[k] / s;//因為後面的回代沒用到這些資料
		A[j + 3] = A[j + 3] - A[j + 2] * A[j + 1];
		b[k + 1] = b[k + 1] - A[j + 2] * b[k];
	}
	s = A[3 * n - 3];
	if (fabs(s) + 1.0 == 1.0)
	{
		cout << "消元后,主對角元素是0,無解!!" << endl;
		return b;
	}
	b[n - 1] = b[n - 1] / s;
	for (k = n - 2; k >= 0; k--)//回代求解方程,相比之下很簡單了,累積求和都沒必要的
		b[k] = b[k] - b[k + 1] * A[3 * k + 1];
	return b;
}

主函式的呼叫:

int main()
{
	int n = 5;
	double A[13]{ 2,-1,-1,2,-1,-1,2,-1 ,-1,2,-1 ,-1,2 };
	double b[5]{ 1,0,0,0,0 };
	double *d;
	d = trde(n, A, b);
	for (int i = 0; i < n; i++)
	cout << d[i] << "   ";
	//這裡直接將解顯示在黑框框裡了
	system("pause");
	return 0;
}

執行結果,原線性方程組,以及輸出的解,如圖5,6所示。
圖5 教材習題(用PC幫著解,偷個懶,O(∩_∩)O哈哈~)
圖6 執行結果
最後把類的形式給出來,和全選主元高斯消元法的形式是一樣的,input(),輸入txt檔名,然後進行操作,a_trde(),輸出結果儲存在txt檔案裡,自己命名,接下來是完整的追趕法的類的原始碼。

//追趕求解三對角方程組
#include  <iostream>
#include  <fstream>
#include  <cmath>
using namespace std;
class  trde
{
private:
	int n;
	double *b, *d;
public:
	trde(int nn)
	{
		n = nn;
		b = new double[3 * n - 2];    //動態分配記憶體空間,一維陣列,存放係數矩陣A的元素
		d = new double[n];//一維陣列存放常數向量b的元素
	}
	void input();  //從檔案讀入存放三對角矩陣的向量B以及常數向量D
	void a_trde();      //執行追趕法
	void output();     //輸出結果到檔案並顯示
	~trde()
	{
		delete[] b;
		delete[] d;
	}
};
void trde::input()      //從檔案讀入存放三對角矩陣的向量B以及常數向量D
{
	int  i;
	char str1[20];
	cout << "\n輸入檔名:  ";
	cin >> str1;
	ifstream  fin(str1);
	if (!fin)
	{
		cout << "\n不能開啟這個檔案 " << str1 << endl; exit(1);
	}
	for (i = 0; i<3 * n - 2; i++)  fin >> b[i];  //讀入三對角矩陣A中的非0元素
	for (i = 0; i<n; i++)  fin >> d[i];       //讀入常數向量d
	fin.close();
}
void trde::a_trde()        //執行追趕法,核心的函式
{
	int k, j, m;
	double s;
	m = 3 * n - 2;//第一步,對於k從0~n-2,執行歸一和消元:
	for (k = 0; k <= n - 2; k++)
	{
		j = 3 * k; s = b[j];
		if (fabs(s) + 1.0 == 1.0)
		{
			cout << "\n程式工作失敗!無解. " << endl;
			return;
		}

		b[j + 1] = b[j + 1] / s;//係數矩陣的歸一化
		d[k] = d[k] / s;//常數向量的歸一化
		b[j + 3] = b[j + 3] - b[j + 2] * b[j + 1];//係數矩陣的消元
		d[k + 1] = d[k + 1] - b[j + 2] * d[k];//消元過程常數向量的變化
	}//第二步:進行回代,倒序進行求解
	s = b[3 * n - 3];
	if (fabs(s) + 1.0 == 1.0)
	{
		cout << "\n程式工作失敗!無解. " << endl;
		return;
	}
	d[n - 1] = d[n - 1] / s;
	for (k = n - 2; k >= 0; k--)
		d[k] = d[k] - b[3 * k + 1] * d[k + 1];
}
void trde::output()//結果輸出,自己命名txt檔案,解向量儲存在檔案裡
{
	int i;
	char str[20];
	cout << "解儲存在txt檔案裡,輸入檔名!" << endl;
	cin >> str;
	ofstream fout(str);
	if (!fout)
	{
		cout << "不能開啟這個檔案!!" << str << endl;
		exit(1);
	}
	for (i = 0; i < n; i++)
	{
		fout << d[i] << " ";
		cout << d[i] << " ";
	}
	fout << endl; cout << endl;
	fout.close();
}
//接下來是主函式的呼叫
/*
int main()      //主函式
{
	trde  c(5);
	c.input();  //從檔案讀入存放三對角矩陣的向量B以及常數向量D
	c.a_trde();        //執行追趕法
	c.output();        //輸出結果到檔案並顯示
	system("pause");
	return 0;
}
*/

上週六一看數值分析老師的進度,嚇一跳,原來自己已經落後這麼多了(捂臉哭),所以又學習程式設計實現解三對角線方程組的追趕法,再補充一篇,自己本沒有時間觀念,但是也算體會到了動漫團隊持續一週更新一集動漫的艱辛,若是讓我每一週都學習並用C++實現一個演算法,那我肯定堅持不住了。佩服那些堅持周更的作者們,真是不容易,正好前陣子看了鳴人的原畫師作者黃成希的一席演講,真的很不容易啊,那樣的勞動強度,為我們展示瞭如此精彩的動漫熱血視訊,人家用十年時間成為了最優秀的原畫師,想想我十年能夠成為最優秀的某某某,還真是羞愧難當啊,只能去追求美好生活了,哈哈~