1. 程式人生 > >數值分析(三):C++實現線性方程組的高斯-賽德爾迭代法

數值分析(三):C++實現線性方程組的高斯-賽德爾迭代法

線性方程組的直接解法之後,就輪到迭代解法了,直接解法針對的是低階稠密矩陣,資料量較少,而工程上有更多的是高階係數矩陣,使用迭代法效率更高,佔用的空間較小。
迭代法的最基本思想就是由初始條件,比如說初始解向量隨便列舉一個,就0向量也行,然後進行迭代,k到k+1,一步一步從k=1開始去逼近真實解,雖然說迭代法的解是近似解,但是當迭代次數足夠多的時候,得到的就是很很接近真實解了,而直接解法說是得到精確解,但是受計算機的限制,截斷誤差總是有的,所以最後得到的也是近似解,因此兩種解法也無所謂孰優孰劣之分,得具體情境具體看了,這次來實現迭代法中的高斯-賽德爾迭代法,並用C++實現。
(一)高斯-賽德爾迭代法的步驟
最簡單的迭代法是雅克比迭代法,對於對角嚴格佔優的係數矩陣就可以使用雅克比迭代法進行計算,比如說方程:
圖1 例項方程


滿足係數矩陣的嚴格對角佔優,即對角線上的元素比同行的其他元素之和還要大,數學表示式就是:
圖2 係數矩陣嚴格對角佔優的數學表述
滿足這樣的關係就可以進行雅克比迭代法了,進行雅克比迭代是留下主對角元素在最左邊,然後剩下全部移到右邊,得到這樣一個一步一步迭代的式子:
圖3 雅克比迭代遞推
這樣就到了一個k到k+1的迭代公式,可以取(0,0,0)進行迭代,知道解向量達到一定的精度放棄迭代,十次迭代後,就能得到:
圖4 雅克比迭代法得到近似解
這是迭代法最常見也最容易的雅克比迭代,效率比較低,於是就有高斯—賽德爾迭代法,那就是方程裡迭代計算x2時,x1已經迭代過了,即x1的資料已經更新了,所以應該用最新的值,關鍵是效率會高很多,於是就有下面的迭代式子:
圖5 雅克比迭代法的改進就是高斯-賽德爾迭代法
這樣一種小小的改進,給迭代計算方程的解帶來極大效率的提高,所以接下來再將高斯—賽德爾迭代法步驟進行數學語言的描述:
使用高斯-賽德爾迭代法解線性方組程***Ax=b***:
1:先判斷係數矩陣是否嚴格對角佔優,即針對係數矩陣的每一行驗證,是否滿足主對角元的絕對值比該行其他元素的絕對值之和還要大,即在迴圈裡驗證係數矩陣每一行是否滿足:

滿足了對角佔優,再繼續下一步;
2:
(1)設定初始即第一次迭代時,k=1時的解向量,方便起見,x[i]=0(i=1,2,···n),初始向量設定為零向量;
(2)對於k=1,2,3···N(N視你的精度確定,若是精確到小數點之後很多位,那N就比較大了),然後對於i=1,2,···n:
圖6:高斯-賽德爾迭代法最關鍵的算式
當滿足了精度要求就退出迴圈,計算結束。
(二)C++實現高斯-賽德爾迭代法
同樣的分別用函式和類來實現這個簡單的演算法,首先是函式,還是那個觀點,函式就是一個黑匣子,對於客戶或是任何一個使用者來說,只需要知道輸入引數是什麼,以及自己需要得到什麼結果就行,至於黑匣子中間是什麼,以及如何實現的,就不用你操心了,還記得以前做畢設的時候,有一步是關於工業相機二次開發的,當然二次開發這個名詞有點裝13,但是事實上就是呼叫廠家提供的函式自己寫段程式碼,或是類來控制相機,控制相機的採集影象,曝光時間、白平衡等屬性的控制,以及資料的儲存,當時就很不理解,一個是二次開發有什麼用?明明廠家都提供驅動了,直接開啟驅動直接採集不就行了?答案就是人家驅動的原始碼不可能給你啊;二就是廠家就提供一個開發包給你,開發包裡面是什麼也不知道,就像一個黑匣子一樣,你用就知道了,沒必要知道里面是什麼,當時就很心慌,內部機理都不知道就讓我操作,當時還是太年輕,哎~現在都看開了,因為底層的東西,真的工作量會很大,而且你也不一定能看懂,更關鍵的是你也沒必要重複造輪子,當然也造不出來,還不如人家寫的是什麼,你就用什麼,既然商業化的產品,正規的大廠,品質自然能保證,所以用就行了。回到函式,從設計者的角度來看,這個高斯-賽德爾迭代法需要哪些引數,係數矩陣、常數矩陣、矩陣階數,線性方程的表示就這些,還有相比直接解法,迭代解法跟重要的就是解的精確度要求,所以輸入就是這麼四個引數,輸出嗎,就是一個解向量,於是函式的宣告如下:

double *gauss_seidel(int n, double **a, double *b,double eps)

最後將數學語言翻譯成C++語言就可以得到高斯-賽德爾迭代法的程式設計實現了,比想象中的來得簡單:

double *gauss_seidel(int n, double **a, double *b,double eps)
{
	int i, j;
	double p, t, s, q;
	double *x = new double[n];

	for (i = 0; i <= n - 1; i++)//這個大迴圈判斷係數矩陣是否嚴格對角佔優,是否適用高斯賽德爾迭代法
	{
		p = 0.0; x[i] = 0.0;
		for (j = 0; j <= n - 1; j++)
			if (i != j)   p = p + fabs(a[i][j]);
		if (p >= fabs(a[i][i]))
		{
			cout << "\n程式工作失敗!" << endl;
			return NULL;
		}
	}
	//接下來就是正式的迭代計算
	p = eps + 1.0;
	while (p >= eps)//當精度還沒達到的時候,繼續迭代
	{
		p = 0.0;
		for (i = 0; i <= n - 1; i++)
		{
			t = x[i]; s = 0.0;
			for (j = 0; j <= n - 1; j++)
				if (j != i) s = s + a[i][j] * x[j];
			x[i] = (b[i] - s) / a[i][i];
			q = fabs(x[i] - t) / (1.0 + fabs(x[i]));
			if (q>p) p = q;
		}
	}
	return x;
}

其中,程式設計實現反而較雅克比迭代法簡單,因為迴圈中的值本來就是持續更新的,所以在計算k+1次的時候,用到的就是最新的資料,所以,反倒很容易實現,計算機倒是非常對高斯-賽德爾迭代法的胃口。
筆者的主函式呼叫如下:

int main()
{
	double **A, *b, eps;
	int n;
	cout << "輸入係數矩陣的階數n:" << endl;
	cin >> n;
	cout << "依次輸入係數矩陣的每一個元素A[i][j]:" << endl;
	A = new double *[n];
	for (int i = 0; i < n; i++)
		A[i] = new double[n];
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			cin >> A[i][j];
	b = new double[n];
	cout << "輸入常數向量每一項b[i]:" << endl;
	for (int i = 0; i < n; i++)
		cin >> b[i];
	double *x = new double[n];
	cout << "輸入計算解的精度要求eps:" << endl;
	cin >> eps;
	x = gauss_seidel(n, A, b, eps);
	cout << "通過高斯—賽德爾迭代法計算得到的解:" << endl;
	for (int i = 0; i < n; i++)
		cout << "x"<<i<<"="<<x[i] <<endl;
	system("pause");
	return 0;
}

當然寫成類就沒必要在主函式這麼矯情了,直接用txt檔案進行資料的讀入,輸入的結果也儲存在txt檔案裡面,類的實現如下:

//3GSDL.CPP
//Gauss-Seidel迭代法
#include  <iostream>
#include  <fstream>
#include  <cmath>
using namespace std;

class  gsdl
{
private:
	int n;
	double  **a, *b, *x, eps;
public:
	gsdl(int nn)
	{
		int i;
		n = nn;
		a = new double*[n];   //動態分配記憶體空間
		for (i = 0; i<n; i++) a[i] = new double[n];
		b = new double[n];
		x = new double[n];
	}
	void input();    //從檔案讀入係數矩陣A、常數向量B以及eps
	void a_gsdl();    //執行Gauss-Seidel迭代法
	void output();   //輸出結果到檔案並顯示
	~gsdl()
	{
		int i;
		for (i = 0; i<n; i++) { delete[] a[i]; }
		delete[] a;
		delete[] b, x;
	}
};

void gsdl::input()      //從檔案讀入係數矩陣A、常數向量B以及eps
{
	int  i, j;
	char str1[20];
	cout << "\n輸入檔名:  ";
	cin >> str1;
	ifstream  fin(str1);
	if (!fin)
	{
		cout << "\n不能開啟這個檔案 " << str1 << endl; exit(1);
	}
	for (i = 0; i<n; i++)                       //讀入矩陣A
		for (j = 0; j<n; j++)  fin >> a[i][j];
	for (i = 0; i<n; i++)  fin >> b[i];          //讀入常數向量B
	fin >> eps;                              //讀入eps
	fin.close();
}

void gsdl::a_gsdl()       //執行Gauss-Seidel迭代法
{
	int i, j;
	double p, t, s, q;
	for (i = 0; i <= n - 1; i++)
	{
		p = 0.0; x[i] = 0.0;
		for (j = 0; j <= n - 1; j++)
			if (i != j)   p = p + fabs(a[i][j]);
		if (p >= fabs(a[i][i]))
		{
			cout << "\n程式工作失敗!" << endl;
			return;
		}
	}
	p = eps + 1.0;
	while (p >= eps)
	{
		p = 0.0;
		for (i = 0; i <= n - 1; i++)
		{
			t = x[i]; s = 0.0;
			for (j = 0; j <= n - 1; j++)
				if (j != i) s = s + a[i][j] * x[j];
			x[i] = (b[i] - s) / a[i][i];
			q = fabs(x[i] - t) / (1.0 + fabs(x[i]));
			if (q>p) p = q;
		}
	}
}

void gsdl::output()       //輸出結果到檔案並顯示
{
	int  i;
	char str2[20];
	cout << "\n輸出檔名:  ";
	cin >> str2;
	ofstream fout(str2);
	if (!fout)
	{
		cout << "\n不能開啟這個檔案 " << str2 << endl; exit(1);
	}
	fout << endl;  cout << endl;
	for (i = 0; i<n; i++)
	{
		fout << x[i] << "   ";
		cout << x[i] << "   ";
	}
	fout << endl;  cout << endl;
	fout.close();
}

void main()      //主函式
{
	gsdl  c(3);
	c.input();         //從檔案讀入係數矩陣A、常數向量B以及eps
	c.a_gsdl();        //執行Gauss-Seidel迭代法
	c.output();        //輸出結果到檔案並顯示
	system("pause");
}

就這樣完成了高斯-賽德爾迭代法的C++實現,今天貌似心情不好啊,(*  ̄︿ ̄),寫點東西沒點激情,有點喪,其實喪真的是常態,前陣子聽個簡單的講座,關於如何克服拖延的,結果老師到場,就直接說道,這主題不對啊,你們現在還有心情探討怎麼克服拖延,在我看來,你們就是矯情,你們就是太閒了,沒有體會過那種事情辦砸了的糟糕體驗,以及沒有感受到競爭的激烈性,一片廝殺的紅海了,哪還有心情拖延,最好立即、馬上去學習,不知道學什麼的話,最起碼把英語學好,哈哈哈哈,蠻有道理(事情應該被誇張了,不過意思差不多啦,這樣敘述反而更有表現力,反正是正能量的東西,就不想較真了。)雖然我不認為自己是個拖延的人,但是還是覺得好有道理,筆者還是好好培養自己的執行力了~