函数f(x)=x^3-x-1在[1,2]上有单根,要求精度在10^-5。
二分法:
public class Half {
public static double fx(double x) {
return x*x*x-x-1;
}
public static void main(String []args) {
int count=0;
double precesion=0.00001;
double a=1,b=2;
double half=0;
while(fx(a)*fx(b)<0) {//当a,b区间有根时
half=(a+b)/2.0;
if(fx(half)>0) {//不断将区间分为原来的一半
b=half;
} else {
a=half;
}
count++;//迭代次数 ++
if(fx(b)-fx(a)<=precesion) {//如果解的精度达到要求
break;
}
}
System.out.println(half+" "+count);
}
}
二分法是一个比较简单易懂的方法,就不详细说了。
迭代法:
public class Iteration {
//迭代法 最主要的是构造一个 收敛的 迭代函数 如这题
//x=φ(x)=(x+1)^(1/3)开三次方,是收敛的,如果换成别的,像
//x=φ(x)=x^3-1 是发散的,找不到结果的
public static double φ(double x) {
return Math.pow(x+1,1/3.0);
}
public static void main(String []args) {
int count=0;
double x0=1,x1=0;
double precision=0.00001;
while(true) {
count++;
x1=φ(x0);
if(Math.abs(x1-x0)<=precision) {
break;
}
x0=x1;
}
System.out.println(x1+" "+count);
}
}
迭代法是由x1作为迭代函数的值,x0作为参数,一轮迭代后比较二者差值与精度,不够再将x1赋给x0,作为下一轮迭代。。。。对于函数由本来的f(x)=0,变为x=φ(x) 迭代函数,进行一个压缩变换,是有解的关键。
牛顿迭代法:
有的迭代法虽然可以求解,但是迭代速度太慢,打不到要求,于是更加快速的Newton迭代法出炉了
public class NewtonIteration {
private final static double precesion=0.00001;
public static double Derivative(double x0) {
//用 △fx/△x 精度为0.00001 做 导数
return (f(x0+precesion)-f(x0))/precesion;
}//导数我是求出它的近似解
public static double f(double x) {
return x*x*x-x-1;
}
public static void main(String[]args) {
double x0=1;
double x1=0;
int count=0;
while(Derivative(x0)!=0) {
x1=x0-f(x0)/Derivative(x0);
if(Math.abs(x1-x0)<=precesion) {
break;
}
count++;
x0=x1;
}
System.out.println(x1+" "+count);
}
}
只要理解了上面那副图就很容易写出代码。但是这个方法很依赖于初值的选择,只要初值选不好,就很容易导致最后结果发散,形成死循环
弦截法:
避免了牛顿法的求导问题,用曲线上两点之间形成的直线与x轴交点做下一个x
两点之间直线的公式 ax+by+c=0,只要确定a,b,c参数就可以做了
public class SecantMethod {
public static double f(double x) {
return x*x*x-x-1;
}
public static void main(String[]args) {
double a=1,b=2;
double precesion=0.00001;
int count=0;
System.out.println("弦截法");
while(true) {
a=a-f(a)*(b-a)/(f(b)-f(a));//弦与 x轴的交点
if(Math.abs(f(a))<=precesion) {
break;
}
count++;
}
System.out.println(a+" "+count);
}
}
还有对迭代方向有优化的牛顿下山法,Aitken算法:
Aitken算法是利用两次暂时的迭代减小导数对计算精度的影响,并缩小每次迭代的误差。
package find_root;
public class Aitken {
//和牛顿下山法 差不多 都是对每一轮 近似的根 进行补偿
//但是 这里可以避免导数的误差对 计算精度的影响
public static double f(double x) {//迭代函数x=e^-x
return Math.exp(-x);
}
public static void calculation() {
double x0=0.5,x1=0,xt1=0,xt2=0;
int count=0;
while(true) {
xt1=f(x0);//第一个暂时变量
xt2=f(xt1);//第二个
x1=xt2-(xt2-xt1)*(xt2-xt1)/(xt2-2*xt1+x0);//用两个暂时变量
//的线性组合 , 减小近似点与解的差距,提高迭代速度
count++;
if(Math.abs(x1-x0)<0.00001) {
break;
}
x0=x1;
}
System.out.println(x1+" "+count);
}
public static void main(String[]args) {
calculation();
}
}
牛顿下山法:由于牛顿迭代法对于初值的选择比较重要,限制了他的应用,于是就增加一个常数C xk+1=xk-C*f(xk)/f’(xk) 使得 |f(k+1)|<|f(k)|,每一轮的C都可能不同,一般C=1,然后一个循环,找到合适的C,再参与迭代。
package find_root;
public class Downhill_method {
private final static double precesion=0.000001;
public static double derivative(double x0) {
//用 △fx/△x 精度为0.00001 做 导数
return (f(x0+precesion)-f(x0))/precesion;
}
public static double f(double x) {
return x*x*x-x-1;//迭代函数 x=x^3-x-1
}
public static void calculation() {
double x0=1.5,x1=0;
int count=0;
while(true) {
for(double C=1;;C=C*1.0/2){//下山因子 防止迭代发散
x1=x0-C*f(x0)/derivative(x0);
if(Math.abs(f(x1)-f(x0))<precesion) {
break;
}
}
count++;
if(Math.abs(x1-x0)<precesion) {
break;
}
x0=x1;
}
System.out.println(x1+" "+count);
}
public static void main(String[]args) {
calculation();
}
}
如有需要详细的数学证明的,可以查阅由金聪,熊盛武编写的《数值分析》。