注意这篇文章不是研究科赫雪花的生成原理,而是讨论一个实现科赫雪花的更优的算法,关于分形的一些总结以后再说吧。当然这种思想也可以用于其他分形的程序,只要有足够的思考。我们在写科赫雪花程序中,求顶点是一个难点,因为一般是要考虑不同斜率时顶点的不同情况。如果起始直线是水平的,那通过作图分析,还是很容易编写顶点的程序的:
if ((x3<x4)&&(y3==y4)) {
x5 = (x3 + x4) / 2.0;
y5 = y3 - (x4 - x3) * Math.sqrt(3.0) / 2.0;
}
else if ((x3<x4)&&(y3<y4)) {
x5 = x4 + (x4 - x3);
y5 = y3;
}
else if ((x3>x4)&&(y3<y4)) {
x5 = x4 + 2*(x3 - x4);
y5 = y4;
}
else if ((x3>x4)&&(y3==y4)) {
x5 = (x3 + x4) / 2.0;
y5 = y3 + (x3 - x4) * Math.sqrt(3.0) / 2.0;
}
else if ((x3>x4)&&(y3>y4)) {
x5 = x4 - (x3 - x4);
y5 = y3;
}
else if ((x3<x4)&&(y3>y4)) {
x5 = x3 - (x4 - x3);
y5 = y4;
}
当然我这种写法是比较丑的,因为把0~360°的六种情况全写出来了,其实如果设k=(x3-x4)/(y3-y4),通过k的正负来判断倾斜方向可以让程序更简洁一点。 不过,这种投机的分析方法只能用在直线是水平的这一特殊的情况,如果直线是任意倾斜角度的,那就必须通过计算旋转角找顶点这种强硬的数学方法,程序编写的难度骤然上升。于是,有没有方法可以只通过两个三等分点的坐标通过简单的计算就可以求出所有情况的科赫雪花的顶点呢?答案是肯定的,下面我来介绍。
用三等分点坐标求任意倾斜角的科赫雪花顶点。
首先我给出这种方法的数学证明:如下图所示:
直线的倾斜角是任意的,BC是确定线段上的三等分点,也就是B(x3,y3)和C(x4,y4),这里把原直线忽略了。顶点是A,在科赫雪花的程序中通常就是(x5,y5)。这里作几条辅助线:过B点作一条水平的直线,过C作一条垂直的直线,相交于G,过A点作BC的高,过A点作垂直的直线交BC于F,过D作AF的垂线,垂足为E。为什么要这么作辅助线?因为既然要通过简单的计算得到结果,那充分利用B(x3,y3)和C(x4,y4)只通过加减法就可以得到的数据,比如CG的长度就是y4-y3,BG的长度就是x4-x3;另外就是要考虑顶点的位置,这里可以看到顶点A的坐标可以又D的坐标得出,D的坐标很好算,就是B、C的中点,因此接下来的任务就是通过三角形的关系找出DE和AE的长度。
首先容易证明:△BCG∽△AFD∽△DFE
得到线段之间的比例关系: BG/GC=AD/DF=DE/EF,得DE=AD*EF/DF (1)
此时注意到EF/DF=DF/AF=GC/BC,把EF/DF=GC/BC带入(1)得:DE= AD*GC/BC (2)
注意到AD/BC=AD/AB=√3/2,所以(2)就相当于: DE= GC*√3/2
而GC=y4-y3,x5=xE=(x3+x4)/2-DE 所以:x5=(x3+x4)/2+(y3-y4) *√3/2 (3)
这就是科赫雪花顶点的横坐标公式,适用于任意倾斜角的雪花。如果直线是右倾斜的,那么y3-y4将变成正数,顶点坐标将出现在中点坐标的右方,一样成立。
同样我们可以证明y5=(y3+y4)/2+(x4-x3)*√3/2 (4)
(3)、(4)式的好处在于写科赫雪花程序时无需再考虑倾斜情况,即使起始直线是20°,这个公式也能求出此时雪花顶点的位置,那么程序就简单多了:
void kochsnow(double x1,double y1,double x2,double y2,int depth,Graphics g){
//注意depth请在主函数中自行定义
if(depth<=1){
//完成递归的条件
g.drawLine((int)x1, (int)y1, (int)x2, (int)y2);
}else{
//三等分点坐标
double x3=x1*2/3 + x2*1/3;
double y3=y1*2/3 + y2*1/3;
double x4=x1*1/3 + x2*2/3;
double y4=y1*1/3 + y2*2/3;
//顶点坐标
double x5=(x3+x4)/2+(y3-y4)*Math.sqrt (3)/2;
double y5=(y3+y4)/2+(x4-x3)*Math.sqrt (3)/2;
//连接所有点,递归
kochsnow(x1,y1,x3,y3,depth-1,g);
kochsnow(x3,y3,x5,y5,depth-1,g);
kochsnow(x5,y5,x4,y4,depth-1,g);
kochsnow(x4,y4,x2,y2,depth-1,g);
}
}
这里我们可以优化这个程序:在画板上任意点两个点,行程一片科赫雪花,起始是正三角形,以后每点击一次科赫雪花的层数增加1,完整参考代码如下:
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Koch extends Applet implements MouseListener {
private int xa,ya,xb,yb,depth,count=0;
private double xc,yc;
public void init() {
setBackground(Color.BLACK);
setSize(960, 720);
addMouseListener(this) ;
depth=1;
}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mousePressed(MouseEvent e){}
public void mouseReleased(MouseEvent e) {
depth++;
if(count==0){
xa = e.getX();
ya = e.getY();
count++;
depth=0;
}else if(count==1){
xb = e.getX();
yb = e.getY();
xc=(xa+xb)/2+(ya-yb)*Math.sqrt (3)/2;
yc=(ya+yb)/2+(xb-xa)*Math.sqrt (3)/2;
count=1;
repaint();
}
}
void draw(double x1,double y1,double x2,double y2,int depth,Graphics g){
if(depth<=1){
g.drawLine((int)x1, (int)y1, (int)x2, (int)y2);
}else{
depth--;
double x3=x1*2/3 + x2*1/3;
double y3=y1*2/3 + y2*1/3;
double x4=x1*1/3 + x2*2/3;
double y4=y1*1/3 + y2*2/3;
double x5=(x3+x4)/2+(y3-y4)*Math.sqrt (3)/2;
double y5=(y3+y4)/2+(x4-x3)*Math.sqrt (3)/2;
draw(x1,y1,x3,y3,depth,g);
draw(x4,y4,x2,y2,depth,g);
draw(x3,y3,x5,y5,depth,g);
draw(x5,y5,x4,y4,depth,g);
}
}
public void paint(Graphics g){
g.setColor(Color.WHITE);
draw(xb, yb, xa, ya, depth, g);
draw(xa, ya, xc, yc, depth, g);
draw(xc, yc, xb, yb, depth, g);
}
}
代码说明:
1. 这个程序的设计是点击两次后形成一个正三角形,然后每点击一次会重新找一个顶点,以便于调整三角形的大小来观察科赫雪花的变化,如果不想要让原来的三角形变化那就不要移动鼠标就行了。
2. 程序还可做进一步优化:左键点击深度+1,右键点击深度-1,这个我没写。
3. 这个程序没用面板,用的是applet,不过其实无所谓,核心算法在draw函数里,而且除了x5和y5也没特别厉害的地方,复制参考的话只要把那个函数复制了即可。