這次實作主要是熟悉一些 stl 使用,語法盡可能使用 C++,挑用兩種不同資料結構,結果感到有些意外。
第一種資料結構是單純用指標配置一維 heap,做 index 轉換 ( 就是數值分析習慣用的一維模擬二維) ;
另一種資料結構採用 vector< vararray<T> >。先把意外的結論寫下來。
valarray 在 vc 底下之操作並沒顯得優勢 ,反之所費時間約為一般陣列存取之兩倍;而 g++ 下之 valarray 確實有操作上之優勢。
另外大陸的行(row)列(column),與台灣的行(column)列(row) 在中文上是相反的,感到困惑網友請注意這點。
[1] 原理篇
這裡用的求反矩陣,必須有一份暫存矩陣,而不是原地做。高斯消去法求反矩陣不熟請參考其他相關資料,這裡講的是一些小細節注意地方。假設欲求矩陣 src 之反矩 dst,一開始 dst 先設單位矩陣。在簡化過程之第 k 個迭代時,
a. 列交換( row swap, Rij)
若要將 src 第 i 列 / 第 j 列互換時,由於第 i 列與第 j 列前面 k 個元素都一定被消成 0 了,所以做列互換時只需行第 k 個元素交換即可;但 dst 一定要所有元素都經過交換。
b. 行交換 (column swap, Cij)
將 src 第 i 行 / 第 j 行互換時,這沒話說,不論是 src 還是 dst 必將所有元素全都交換
c. 第 i 列乘以 v 加到第 j 列 (Rij (v) )
和列交換相仿,src 由於前面 k 個元素必被消成 0 ,所以做這動作時,只需從第 k 個元素開始執行即可;而 dst 則必須所有元素都進行這步驟。
[2] 程式碼與執行結果
這份程式碼頗長,由於是拿來做測時比較,故上述的列交換、Rij(v) 等都沒經過化簡,程式碼如下。
- #include <iostream>
- #include<algorithm>
- #include <cmath>
- #include <vector>
- #include <valarray>
- #include <ctime>
- using namespace std;
- template<typename T>
- bool minv1(const T * src, T * dst, const size_t n, const T eps)
- {
- const size_t n2=n*n;
- size_t i, j, k, is, js;
- T pivot, tmp;
- T * mbk = new T [n2];
- copy(src, src+n2, mbk); // copy src to mbk
- fill(dst, dst+n2, 0); // set dst to unit matrix
- for(i=0; i<n; ++i) dst[i*n+i]=1;
- for(k=0; k<n; ++k){ // execute k times
- pivot = 0; // find pivot
- for(i=k; i<n; ++i) for(j=k; j<n; ++j)
- if(pivot < (tmp = abs(mbk[i*n+k])))
- pivot=tmp, is=i, js=j;
- if(pivot<=eps){ // has no solution
- delete [] mbk;
- return false;
- }
- if(is!=k){ // swap row(is,k)
- swap_ranges(mbk+k*n, mbk+k*n+n, mbk+is*n);
- swap_ranges(dst+k*n, dst+k*n+n, dst+is*n);
- }
- if(js!=k){ // swap col(js,k)
- for(i=0; i<n; ++i){
- swap(mbk[i*n+k], mbk[i*n+js]);
- swap(dst[i*n+k], dst[i*n+js]);
- }
- }
- pivot = mbk[k*n+k]; // adjust pivot to 1
- for(j=0; j<n; ++j){
- mbk[k*n+j]/=pivot;
- dst[k*n+j]/=pivot;
- }
- // elimination
- for(i=0; i<n; ++i){
- if(i!=k){
- tmp = -mbk[i*n+k];
- for(j=0; j<n; ++j){
- mbk[i*n+j]+=tmp*mbk[k*n+j];
- dst[i*n+j]+=tmp*dst[k*n+j];
- }
- }
- }
- }
- delete mbk;
- return true;
- }
- template<typename T>
- bool minv2(vector< valarray<T> > src, vector<valarray<T>> & dst, const size_t n, const T eps)
- {
- const size_t n2=n*n;
- size_t i, j, k, is, js;
- T pivot, tmp;
- valarray<T> row_tmp(n);
- for(i=0; i<n; ++i) dst[i][i]=1;
- for(k=0; k<n; ++k){ // execute k times
- pivot = 0; // find pivot
- for(i=k; i<n; ++i) for(j=k; j<n; ++j)
- if(pivot < (tmp = abs(src[i][k])))
- pivot=tmp, is=i, js=j;
- if(pivot<=eps) // has no solution
- return false;
- if(is!=k){ // swap row(is,k)
- row_tmp = src[is], src[is] = src[k], src[k]=row_tmp;
- row_tmp = dst[is], dst[is] = dst[k], dst[k]=row_tmp;
- }
- if(js!=k){ // swap col(js,k)
- for(i=0; i<n; ++i){
- swap(src[i][k], src[i][js]);
- swap(dst[i][k], dst[i][js]);
- }
- }
- pivot = src[k][k]; // adjust pivot to 1
- src[k]/=pivot, dst[k]/=pivot;
- // elimination
- for(i=0; i<n; ++i){
- if(i!=k){
- tmp = -src[i][k];
- src[i]+=tmp*src[k];
- dst[i]+=tmp*dst[k];
- }
- }
- }
- return true;
- }
- template<typename T>
- void gen(T *& arr, const size_t n){
- const size_t n2=n*n;
- for(size_t i=0; i<n2; ++i)
- arr[i] = rand() % 10;
- }
- int main()
- {
- const size_t n = 1000;
- const double eps=1e-9;
- double *x = new double[n*n];
- double *y = new double[n*n];
- vector< valarray<double> > vva(n, valarray<double>(n));
- vector< valarray<double> > vvb(n, valarray<double>(n));
- clock_t ck;
- gen(x,n);
- for(size_t i=0; i<n; ++i) for(size_t j=0 ; j<n; ++j)
- vva[i][j] = x[i*n+j];
- ck = clock();
- minv1(x, y, n, eps);
- cout << "minv1 : " << clock()-ck << endl;
- ck = clock();
- minv2(vva, vvb, n,eps);
- cout << "minv2 : " << clock()-ck << endl;
- delete [] x;
- delete [] y;
- system("pause");
- return 0;
- }
上面要做輸出還是幹嘛的自己再加,這裡不附。
vc2010 , release 預設參數(含O2) 執行結果如下
minv1 : 7328
minv2 : 14276
Code::Blocks 10.5 (gcc4.4.1) ,-O2,執行結果如下
minv1 : 8423
minv2 : 7143
針對這結果其實蠻意外的,valarray 在 vc 底下之操作並沒顯得優勢 ,反之所費時間約為一般陣列存取之兩倍(其實網路上找得到有人針對 vc 下之 valarray 存取做處理,但速度也只與 pointer + heap 相仿);而 g++ 下之 valarray 確實有操作上之優勢。
假設變數是
double tmp = 2.0;
valarray<double> a(10), b(10);
但 valarray 很尷尬的是,我們只能寫成
b = tmp * sin(a);
這樣上述的精簡技巧就完全不能用,因實際要做的可能只會是
b[2:9] = tmp * sin(a [2:9] )
至於迴圈精簡後的程式碼,下一份供參考。
- template<typename T>
- bool minv(const T* src, T* dst, const size_t n, const T eps)
- {
- const size_t n2=n*n;
- size_t i, j, k, is;
- T pivot, tmp;
- T * mbk = new T [n2];
- copy(src, src+n2, mbk); // copy src to mbk
- fill(dst, dst+n2, 0); // set dst to unit matrix
- for(i=0; i<n; ++i) dst[i*n+i]=1;
- for(k=0; k<n; ++k){ // execute k times
- pivot= abs(mbk[k*n+k]), is=k;
- for(i=k+1; i<n; ++i) // find pivot
- if(pivot < (tmp = abs(mbk[i*n+k])))
- pivot=tmp, is=i;
- if(pivot<=eps){ // has no solution
- delete [] mbk;
- return false;
- }
- if(is!=k){ // swap row(is,k)
- swap_ranges(mbk+k*n+k, mbk+k*n+n, mbk+is*n+k);
- swap_ranges(dst+k*n, dst+k*n+n, dst+is*n);
- }
- pivot = mbk[k*n+k]; // adjust pivot to 1
- for(j=k; j<n; ++j) mbk[k*n+j]/=pivot;
- for(j=0; j<n; ++j) dst[k*n+j]/=pivot;
- // elimination
- for(i=0; i<n; ++i){
- if(i!=k){
- tmp = -mbk[i*n+k];
- for(j=0; j<n; ++j) dst[i*n+j]+=tmp*dst[k*n+j];
- for(j=k; j<n; ++j) mbk[i*n+j]+=tmp*mbk[k*n+j];
- }
- }
- }
- delete mbk;
- return true;
- }
一些更細節的問題,像是回圈合併,這個就不再做撰碼討論。
留言列表