最近在做心電相關的專案,由於微控制器的處理能力有限,在接收心電訊號之後需要對資料進行壓縮(其實是取一些特徵點),然後後期再進行顯示。但是在手持arm上進行顯示的時候,通過這些殘缺的資料繪出心電圖形是很困難的,這就要進行插值處理,所以進行了一些插值演算法相關的研究,常用的插值演算法是拉格郎日插值和牛頓插值演算法,切比雪夫插值演算法。
一. 拉格朗日插值
拉格郎日的原理,下面這篇帖子寫的比較好,感興趣可以看一下,對於應用型達人來講,不必對原理過於糾結。
複雜的公式:
其中每個
為拉格朗日基本多項式(或稱插值基函式),其表示式為:
拉格朗日基本多項式
的特點是在
上取值為1,在其它的點
上取值為0。
拉格郎日插值的本質就是通過一些離散的點**乙個函式,然後得到未知點所對應的函式值。很容易想到的就是n階多項式。
f(x) = a0*x^n+a1*x^(n-1)+……an
那我們求得係數就可以了。實際上,我們正是通過拉格朗日公式來解決這個問題,原理見上面貼子。
下面是使用該插值演算法時所遇到的問題:
1. 真實的心電訊號可能都是大於0的值。如果通過所有的離散點進行擬合,則從得到的擬合函式中所獲得的插值容易產生負值,一開始不理解,還以為是自己演算法問題,後來想想,為了使得到的函式穿過所有這些離散點,得到負值是完全有可能的。這就是常見的拉格朗日問題,當次數較高時,會出現數值不穩定的現象(龍格現象)。解決辦法就是分段低階插值。
2. 我改用了3點插值,3點插值也會出現負值,雖然頻率少了一些。因為3點其實構造的就算拋物線,當點的分布不均勻(2,3點離的很近)時,很容易會出現負值。如下:
3. 所以,最後只能採用兩點差值來實現趨勢的描繪,這是純折線的,而且就是線性插值,其實完全沒有必要用拉格朗日了。
……然後發現拉格朗日在實際中的應用很少,因為存在數值不穩定性的特性。
測量資料會有誤差,構造的函式還會有誤差,計算機計算浮點數也有誤差,所以,最終可能會造成很大的誤差。所以,雖然我們有了這個理論基礎,但是在實際應用的時候,仍然需要根據特殊的情況進行編寫,以適應我們的應用。比如可以讓距離間隔差不多的幾個點來進行3點插值,這樣拋物線會平滑,就不會產生負值了,當然,這只是針對我的這種情況而言。再比如,可以進行3點滑動插值,每次插值完成後,移除第乙個點,既每次插值都會保留上次所使用的插值點。
4. 由於拉格朗日插值演算法在插值點增加或者減少時,需要重新計算基本多項式,這樣效率會很低。為了解決這個問題,引入了牛頓插值法.
拉格朗日**實現:
//arrx[n],arry[n]分別存放的是已知插值節點(xi,yi)中的xi,yi,引數n為已知插值節點的個數,而引數x為待求解的插值節點的橫座標值
float lagrange(float arrx,float arry,int n, floatx)
float yresult = 0.0;
float lvalue[80];
intk,m;
floattemp1,temp2;
for(k=0;k
temp1 = 1.0;
temp2 = 1.0;
for(m=0;m
if(m==k)
continue;
temp1 *=(x-arrx[m]);
temp2 *=(arrx[k]-arrx[m]);
lvalue[k]=temp1/temp2;
for(int i=0;i
yresult+=arry[i]*lvalue[i];
returnyresult;
二. 牛頓插值演算法
牛頓插值法的公式如下:
其中,f[x]的求解如下:
牛頓插值演算法的關鍵在於獲得係數f(xk)。我們就以作者所取的3個點(0, 1), (2, 2), (3, 4)來**。根據公式,我們需要取得f(x1), f(x1,x2), f(x1,x2,x3).首先我們分別計算一下值,這是數學計算。
f(x1) = 1;
f(x1,x2) = (f(x2)- f(x1))/(x2- x1) = 1/2
f(x2,x3) = 2. 計算同上,雖然這不是有效係數,但是在進一步求係數的時候會用到。
f(x1,x2,x3) = (f(x2, x3)- f(x2, x3))/(x3- x1) = 1/2
所以,f(x) = 1+1/2*(x-0)+1/2(x-0)(x-2)
這就是理論學習中牛頓插值演算法所謂的差商原理。
這一步數學計算的目的可以加深大腦對計算過程的理解,推薦演算一遍。
下面是程式實現,僅僅有理論是不夠的,還得寫出**。
首先,我們需要兩個陣列,x[3],y[3],分別存放3個橫座標和對應的縱座標值。
x[3] = , y[3] = .
關鍵的問題還是求係數,作者給我們提供的思路是使用二維陣列來存放,我推演一遍。二維陣列a[i][j]的大小根據自己的需要來定。
先給出結果:
當j=0的時候,a[i][0] = y[i].
當i>=1,j>=1的時候,a[i][j] = (a[i][j-1]-a[i-1][j-1])/(x[i]-x[i-j])
自己歸納一下就可以得到上面的結論。
a[0][0] = y[0] = 1;
a[1][0] = y[1] = 2; a[1][1] = (a[1][0]-a[0][0])/(x[1]-x[0]) = (2-1)/(2-0) = 1/2
a[2][0] = y[2] = 4; a[2][1] = (a[2][0]-a[1][0])/(x[2]-x[1]) = (4-2)/(3-2) = 2
a[2][2] = (a[2][1]-a[1][1])/(x[2]-x[0]) = (2-1/2)/3 = 1/2
所以,f(x) = 1+1/2*(x-0)+1/2(x-0)(x-2)
有效的係數全部存在了a[i][i](i=j的時候)中。
有了上面的二維陣列,就可以很容易的實現牛頓插值演算法了。
牛頓**實現:
插值係數計算:
//求得係數,length為已知的插值點個數
for(i=0;i
for(j=0;j<=i;j++)
if(j==0)
a[i][j] =y[i];
continue;
a[i][j] = (a[i][j-1]-a[i-1][j-1])/(x[i]-x[i-j]);
//牛頓插值演算法
float newton(float arrx, float arry,float factor[40],int n,intx)
float result = arry[0];
float tmp = 1.0;
inti,j;
for(i=1;i
for(j=0;j<=i;j++)
if(j!=i)
continue;
tmp *= (x-arrx[i-1]);
result += factor[i][j]*tmp;
returnresult;
三. 切比雪夫插值
切比雪夫插值的提出是為了改善插值在差值區間上的最大誤差控制
關鍵:切比雪夫插值節點的選取
對於乙個插值區間 [a, b], 如果要在這個插值區間上選取n個點作為插值基點,使得上面的最大誤差
最小,則基點的選法如下:
對於 i = 1, 2, ...., n. 下面的不等式在區間 [a, b] 上滿足
切比雪夫的實現就是在牛頓插值中選取合適的基點,並且要用到cos函式。這個演算法感興趣的可以自己研究一下。
插值查詢演算法
插值查詢演算法是基於二分查詢演算法的,只是在查詢的過程中計算middle的方式有所改變,比如一組可以通過二分查詢演算法計算出的nmid索引的值,是這樣子計算的 nmid nlow nhigh 2,但是插值查詢演算法是根據比率算出的,nmid nlow key narr nlow narr nhigh...
插值查詢演算法
在均勻分布的有序數列中,會比二分查詢次數少很多 思想跟二分查詢一直,都是找到其中乙個值,小了往左找,大了往右找 區別是 也跟二分查詢差不多,就公式不同,不過因為公式複雜了一些,需要一些判斷條件來保證它的正確性 public static int binarysearch int arr,int ke...
Catmull Rom插值演算法
一 簡要介紹 catmull rom演算法保證兩點 1 點pi的一階導數等於pi 1 pi 1,即點pi的切向量和其相鄰兩點連線的切向量是平行的 2 穿過所有pi點。這是與貝塞爾曲線的最大區別,正因為這樣的特性,使得catmull rom演算法適於用作軌跡線演算法。點pi處的切線記作 pi 1 pi...