數值分析(五) C 實現一般實矩陣的QR分解

2021-09-01 16:04:38 字數 4010 閱讀 4235

先簡單介紹一下一般實矩陣的qr分解是什麼;

再是原始碼,不過這次的c++原始碼是教材上的,提前說明,才五十幾行**就實現了一般實矩陣的qr分解,精煉且巧妙,若是我來寫肯定是辦不到這麼簡潔的,正如《c++ primer》裡作者所提倡的簡潔是一種美德,這些**簡直就可以修身齊家治國平天下了;

然後再執行這個程式,得到結果,到後面再仔細地分析這個演算法的過程(我自己清楚了,可能表達上也會無法傳達,因為可能部落格這東西更多的可能是寫給自己看的······)。

知道什麼是一般實矩陣的qr分解了,就給出原始碼,按照函式的觀點,輸入的引數是乙個矩陣,這裡實際上是二重指標,直接在函式體裡面將矩陣的分解結果在黑框框裡列印出來,以下是原始碼,以及執行的結果:

#includeusing namespace std;

//接下來把一般實矩陣的qr分解按函式的形式稍稍改寫一下,輸入是一般mxn實矩陣a,以及矩陣的行數m列數n,輸出是qr形式的正交矩陣和上三角矩陣的乘積,

void maqr(double ** a, int m, int n)//進行一般實矩陣qr分解的函式

alpha = 0.0;

for (i = k; i <= m - 1; i++)

if (a[k][k] > 0.0) u = -u;

alpha = u * sqrt(alpha);

if (fabs(alpha) + 1.0 == 1.0)

u = sqrt(2.0*alpha*(alpha - a[k][k]));

if ((u + 1.0) != 1.0)

//左乘矩陣q,迴圈結束後得到乙個矩陣,再將這個矩陣轉置一下就得到qr分解中的q矩陣

//也就是正交矩陣

for (j = k + 1; j <= n - 1; j++)

//h矩陣左乘a矩陣,迴圈完成之後,其上三角部分的資料就是上三角矩陣r

a[k][k] = alpha;

for (i = k + 1; i <= m - 1; i++) a[i][k] = 0.0;

} }for (i = 0; i <= m - 2; i++)

for (j = i + 1; j <= m - 1; j++)

//qr分解完畢,然後在函式體裡面直接將q、r矩陣列印出來

for (i = 0; i> m >> n;

double** a = new double* [m];

for (int i = 0; i < m; i++)a[i] = new double[n];

cout << "輸入矩陣a的每乙個元素" << endl;

然後更仔細地分析矩陣的qr分解:

先用自然語言說一遍,自然語言講究傳神,傳達最大的輪廓,用我的理解來說一遍:一般實矩陣的qr分解,q是正交矩陣,r是上三角矩陣,給定乙個mxn的實矩陣,列向量線性無關,所以就可以分解成mxm的正交矩陣,和mxn的上三角矩陣。

最重要的一步,就是找到n-1個初等反射矩陣h把a對角線以下的列向量全部化成單位向量(或是其倍數,方向由原向量中的最大的元素的符號所決定),這樣乘乙個h矩陣就把相應的一列對角線以下元素化成了0,這個a矩陣有n列,就要求n-1個這樣的h矩陣,然後是不斷左乘a矩陣,直到最後成了乙個上三角矩陣,也就是所求的上三角矩陣r,那麼q矩陣呢?q矩陣的就是所有h矩陣的累乘得到的矩陣,最後再求逆,不過,好在這些正交矩陣相乘仍然是正交矩陣,所以只要把這些累乘得到的矩陣轉置一下就是q矩陣;

然後結合圖再說一遍:

矩陣qr分解的過程,就是找到n-1個初等反射矩陣h,然後左乘a矩陣,使得紅線所圈住的向量全部化成只有首元素不為0的單位向量(或是其倍數,,,),寫成公式就是:

這就是矩陣的qr分解。可見,其中最關鍵的就是初等反射矩陣h,要根據原向量找到這麼乙個初等反射矩陣h,將原向量對映成乙個與僅首元素不為0的單位向量平行的向量,那麼怎麼找h矩陣呢?

設向量w滿足w**置)w=1,則矩陣h=i-2ww**置),就是初等反射矩陣h,也有乙個比較有逼格名稱,叫豪斯霍爾德變換,這個矩陣寫完整就是:

所以只要向量u非零,那麼矩陣:

也是初等對映矩陣,畢竟就是係數的問題嗎,很容易理解,那麼怎麼根據原向量,來找到這麼乙個特殊的矩陣?

這個矩陣有非常重要的幾何意義,那就是乙個向量經過這個矩陣的作用,得到乙個新向量,新舊向量是鏡面對稱的,鏡面就是w向量的法平面,所以就有這麼乙個約化定理,對於非零向量x,總是存在這麼乙個初等反射矩陣,將其對映成單位向量,即hx=-a(1,0,0···,0);

這個a就是單位向量的倍數,數值就是x向量的模,剛剛也說了新舊向量是鏡面對稱的,自然向量長度是相等,然後根據向量x計算得到u向量,大小就是原向量的模,方向和x中元素最大的那個方向一致進而得到h矩陣的公式如下:

所以在本次程式設計中的第一步,就是計算h矩陣。因為要計算n-1個這樣的h矩陣,所以接下的**都是寫在乙個k從1~n-1的迴圈裡面的(教材這裡的u和上面的向量w是一樣的,而與上面的模不是1的u向量有區別)。

是的,計算公式有點複雜,理解也有點難度,不過抓住h矩陣的幾何意義就很好理解了,我怕說太多反而會造成誤解,所以就直接上圖了,σ是原向量的模,ρ是和h計算公式中的β是意義一樣的,目的就是得到模為1的u向量,所以就是乙個係數的問題。

然後是不斷用h矩陣左乘q(q矩陣的初始值就是乙個單位矩陣),所以就相當於h矩陣自身的不斷累乘,最後轉置這個矩陣就得到了正交矩陣q,這也是這段**比較吊詭,也是最巧妙的地方,縱使求了這麼多遍h矩陣,**中自始至終都沒有出現儲存h矩陣的資料結構,上面的截圖也提示了,計算h矩陣,是通過計算u向量的,她計算出u向量,直接將u向量的元素,u1,u2,,,這些元素賦值給a矩陣的相應的位置,然後進行左乘q,公式如下:

這樣賦值的結果,竟然和左乘h矩陣的公式是一樣的,筆者用紙筆驗證過了,哈哈哈,其實能夠這樣寫,而不按照矩陣乘法公式寫的原因主要還是h矩陣的特殊性造成的,畢竟是對稱的正交矩陣,這樣的矩陣很特殊了;(2333333~,若是我來寫這個演算法的話,我估計還要老老實實儲存一下h矩陣)

接著,再左乘a矩陣,同樣的公式:

迴圈了n-1次之後最後得到乙個矩陣不是乙個上三角矩陣,但是她的對角線以上的資料就是r的資料,因為剛剛也說了對角線一下的資料是用於計算h矩陣的u向量的資料,至於最後不斷累乘之後,成了什麼樣,也不需要關注了,只要把累乘後的矩陣的對角線以上的部分提取出來就可以了。

這樣在乙個大迴圈裡就完成了一般實矩陣的qr分解啦~

哎呀呀呀,感覺沒講清楚啊,哈哈哈~

這麼大段的文字,結果道理又沒講清楚,真是哭笑不得了,下次還是不寫這麼大段的文字了,估計還是自己太懶了~

兩本教材是《數值分析第五版》,以及《常用演算法程式集-c++語言描述(第四版)》(清華大學出版社),這兩本書學習起來,絕搭。

一般PID的C語言實現

先看看pid的結構框圖 pid是自動控制演算法裡面最經典,同時也是最簡單的乙個演算法。其經典與簡單程度類似物理學中的牛頓力學三大定律。pid的中心思想是通過誤差來控制輸出,所以pid通常具有以下幾個關鍵的量。1 輸入量r in 2 輸出量r out 3 誤差 error 輸入量 輸出量 pid的控制...

矩陣乘法的實現(一般形式及單個矩陣的n次冪)

題目描述1 給定m k的布林矩陣a,和k n的布林矩陣b,求布林矩陣的乘積ab。源 1 include intmain b 100 100 c 100 100 scanf d d d m,k,n for i 0 ifor i 0 ifor i 0 i for i 0 i printf n retur...

C 一般線性鍊錶類的C 實現

以下的c 類linklist實現了線性鍊錶的一般操作。可以直接在其他的程式中直接建立它的物件,其中線性表中的資料在此為整型,具體應用的時候可以適當的修改,並可以在此基礎上繼續封裝特定的功能。標頭檔案 linklist.h typedef struct lnode lnode,plinklist cl...