玩玩分形

本文介绍如何使用递归方法绘制科赫曲线,并探讨分形几何的美妙之处。通过具体实例讲解不可旋转与可旋转的科赫曲线的绘制方法,包括计算角度和点坐标的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      在做完画图板之后,就被胡哥忽悠着去做神马分形的东东,不过自己也确实被分形所展示的自然界的图形美所吸引。大家自己百度“分形”之后就会看到许多有趣而又神奇的图片,那些图片都是符合分形美的!分形美是一种异于对称美的自然的另一杰作 !     

      那么什么是分形呢?说的简单点就是“部分与整体以某种形式相似的形,称为分形。”这个定义虽然在真正的分形几何学的定义中也不是很确切,但是我觉得很形象。
      大家现在来看一个简单的分形实例。
一.不可旋转的科赫曲线 
    

     
     在这个实例中我们看到,每条线段在一次分形变换中都被分为了四条线段并且中间的是一个没有底

的正三角形,在经过四次以上变换之后我们发现它变成了一种类似于雪花的形状,怎么样是不是还蛮神

奇的勒!
     那么我们怎么用代码实现这个东东呢?其实只需要我们有一点从画图板中学到的界面知识以及初中

几何知识就可以了!
     但是还存在一个问题不知道你有没有想过,怎么存储这个图形中的信息呢?这是个问题,因为图形

中的点和边都是以指数级增长的,我们以队列亦或是树存储信息也好,都不可能一直那么分形下去,都

会出现内存溢出的现象,问题就在于如何尽可能做更多次的分形!这就要抓住分形的本质--自相似性!

我们发现每次分形的最小微元和整体都是相似的,这就启发我们用递归去做,因为每次做的和上次做的

都是同一件事,这样就可以省去一些不必要的重复存储信息的操作。
    请看代码实例: 
     

//递归的深度;
	private int depth=1;

	//重写重绘的方法;
	@Override
	public void paint(Graphics g) {
		// TODO Auto-generated method stub
		super.paint(g);
		KehePaint(150,500,650,500,this.depth);
	}

	/**
	 * 绘制科赫曲线的方法
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 */
	public void KehePaint(double x1,double y1,double x2,double y2,int depth){
		if(depth<=1){//直接画直线;
			g.drawLine((int)x1,(int)y1,(int)x2,(int)y2);
		}else{
			//找三等份点;
			double x3=(2*x1+x2)/3;
			double y3=(2*y1+y2)/3;
			
			double x4=(x1+2*x2)/3;
			double y4=(y1+2*y2)/3;
			
			//找突出的点;
			double x5=0,y5=0;
			double k=(y4-y3)*(x4-x3);//判断倾斜角
			
			//另一种发方法得到第三个点的坐标----还要做一下斜率的判断
			if(y3==y4){
				x5=(x3+x4)/2;
				y5=y3-(x4-x3)*Math.sqrt(3)/2;
			}else if(k<0){//右上斜
				y5=y4;
				x5=x3-(x4-x3);
			}else if(k>0){//右下斜
				y5=y3;
				x5=x4+(x4-x3);
			}if(x3==x4){//如果出现两个点重合的情况
				x5=x3;
				y5=y3;
			}
			
			//递归调用;
			KehePaint(x1,y1,x3,y3,depth-1);
			KehePaint(x3,y3,x5,y5,depth-1);
			KehePaint(x5,y5,x4,y4,depth-1);
			KehePaint(x4,y4,x2,y2,depth-1);
		}
	}

	//绘制图形的方法;
	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub
		if(e.getButton()==1&&this.depth<12){//如果是左击
			this.depth++;
			this.repaint();
		}else if(e.getButton()==3&&this.depth>1){//如果是右击;
			this.depth--;
			this.repaint();
		}
	}

 
     这种操作至少已经尝试过的两种数据结构多操作那么几次!呵呵~~
二.可旋转的科赫曲线
      那么如果在科赫曲线的基础上加一个角度的变换,就是每次的没有底的正三角形不变,其余的每条

边相对于上一条边的角度不是60多而是0~360之间的任意角我们又如何做呢?这就需要我们在对科赫曲线

做分析。
      我们每次用重复的四条线段代替一条线段用一下公式代替F+F--F+F,这里F代表前进,+代表左旋 
60°角,-代表右旋60°角。那么科赫曲线的构造是不是可以等效于一下描述:

     

 
       我们发现这也是一个递归的过程,但是对于可以旋转的科赫曲线我并没有采取向以上那种递归的方法,因为一旦旋转起来每个点都是不确定的值,所以我们可以换个角度思考,抓住每次变换最重要的部分就是角度!只要能获取这些角度值,再加上一个长度就可确定点了。当然在实际操作中比较头痛的问题还是怎么让我们画出来的图形适合于我们的界面,让图形恰当的显示在界面中还是比较麻烦的,我们可以采取先假定一个长度的形式先确定所画点的最值,然后根据最值对于起点和边的长度做调整,最后在画图形。
      呵呵,看看小可一些代码,在算法的处理上还是比较愚笨的,请多指教啦!Y(^_^)Y
     一.如何获取角 
     

/**
	 * 计算角度队列的方法;
	 */
	public void CulAngleList(){
		for(int i=0;i<this.depth;i++){
			if(angleList.size()==0){//如果是空队列就直接加入三个角度;
				addAngle();
				//复制队列;
				angleList.addAll(tmpAngleList);
				//清空临时队列;
				tmpAngleList.clear();
			}else{
				for(int j=0;j<angleList.size();j++){
					addAngle();
					tmpAngleList.add(angleList.get(j));
				}
				//添加最后的三个角度--一开始并没有考虑;
				addAngle();
				//清空原队列;
				angleList.clear();
				//复制队列;
				angleList.addAll(tmpAngleList);
				//清空临时队列;
				tmpAngleList.clear();
			}
		}
	}
	
	
	public void addAngle(){
		//新建三个Integer对象;
		Integer angle1=new Integer(angle);
		Integer angle2=new Integer(-120);
		Integer angle3=new Integer(angle);
		
		tmpAngleList.add(angle1);
		tmpAngleList.add(angle2);
		tmpAngleList.add(angle3);
		
	}

 
   这里我用来两个角度的队列,一个是角度队列一个是临时的队列,总的感觉还是觉得比较麻烦的,不怎么好。
     二.如何确定每一点。 
      

/**
	 * 计算点队列的方法;
	 */
	public void CulPointList(){
		
		//将上次的点队列清空
		pointList.clear();
		//确定起点;
		p1.x=this.startX;
		p1.y=this.startY;
		pointList.add(p1);
		
		//确定第二点的坐标;
	    p2.x=p1.x+(int)this.length;
	    p2.y=p1.y;
	    pointList.add(p2);
	    
	    
	    //遍历角队列求点
		for(int i=0;i<angleList.size();i++){
			
			//得到角度;
			double angle=((double)angleList.get(i)/180)*Math.PI;
			
			//得到两点;
			Point.Double p1=pointList.get(i);
			Point.Double p2=pointList.get(i+1);
			
			//得到坐标的double值;
			//第一个点
			double x1=p1.getX();
			double y1=p1.getY();
			//第二个点;
			double x2=p2.getX();
			double y2=p2.getY();
			
			////计算直线的斜率;
			double atan=0;
			//确定直线p1--p2的角度;
			if(x1<x2){
				atan=Math.atan((y2-y1)/(x2-x1));
			}else if(x1>x2){
				atan=Math.atan((y2-y1)/(x2-x1))+Math.PI;
			}else if(x1==x2&&y1<y2){
				atan=Math.PI/2;
			}else if(x1==x2&&y1>y2){
				atan=-Math.PI/2;
			} 
			
			//确定第三个点;
			double x3=x2+this.length*(Math.cos(atan-angle)); 
			double y3=y2+this.length*(Math.sin(atan-angle));
			
			
			Point.Double p3=new Point.Double(x3,y3);
			
			
			//判断坐标;
			this.maxX=Math.max(x3, maxX);
			this.minX=Math.min(x3, minX);
			this.maxY=Math.max(y3, maxY);
			this.minY=Math.min(y3, minY);
			
			//加入队列;
			pointList.add(p3);
			
		}
	}

 三.对于初始位置和长度的调整

   

/**
	 * 依据得到的点队列的最值改变长度和初始位置;
	 */
	public void StateChanged(){
		
		double lengthX=maxX-minX;
		double lengthY=maxY-minY;

		//横向采用缩放,纵向采取收缩;
		//权衡两个方向取得适合的长度;
		double k=0;//缩放的比例;
		if(lengthX>lengthY){
			k=size/lengthX;
			this.length=k*length;
		}else{
			k=size/lengthY;
			this.length=k*length;
		}
		
		
		/*************************************确定起点********************************************************/		
		//一另外一种方法计算调整之后的起点坐标;
		//计算点的坐标得出最值;
		maxX=startX+length;
		minX=startX;
		maxY=startY;
		minY=startY;
		CulPointList();
		
		double x1=minX;
		double y1=minY;
		lengthX=maxX-minX;
		lengthY=maxY-minY;
		
	    double x2=(size-lengthX)/2;
	    double y2=(size-lengthY)/2;
		
		//调整起点;
		this.startX=startX-(int)(x1-x2)+10;
		this.startY=startY-(int)(y1-y2)+10;
		/******************************************************************************************************************/		
		
		
		//还必须确定一下最值--哎在做一次
		maxX=startX+length;
		minX=startX;
		maxY=startY;
		minY=startY;
		
	}

 
    真的比较繁琐,有些东东我总是感觉自己老是在重复的做,⊙﹏⊙‖∣ 
    呵呵,展示一下最后的效果图!Y(^_^)YY(^_^)Y

 

   

 
    当然我们的分形几何学中的内容还远不止这些东东,还有神马谢冰司机三角形,pascal三角形,

Julia集的东东,形状也千奇百怪,树形分形,草形分析,.......总之分形构造出了很多美妙的图片!

如果我们深入研究就会发现这个世界真奇妙,我们知道的见到的都太少! 引用一下某人的名言啦“任重

道远~~~”!
  
后记 
      总的来说有这么几点感想。1.对于程序员来说算法才是王道。但是算法这么个东东又是要我们自

己去不断的尝试才可以领悟的。2.BUG这个东东真的很恶心,在计算点的时候我不小心将一个弧度制的角

和一个角度值的角相加然后自己又用变量来表示就没有发现,其实在打印角度结果的时候一开始就差那

么一点,这就需要我们去细心思考,思考那些微小的差别!3.对于系统不同的api我们需要取舍利用,比

如一开我用的是POint类来表示点,但是后来发现即使是一开始的60°最后的线段也总是不水平,这就启

发我换一个我不怎么熟悉的Point2D.Double类,哎~~~及结果真是柳暗花明啊!Y(^_^)Y,一直感觉自己

的思路没有问题可就是没有个结果,结果我们换了一个api类就搞定啦!4.这次做的几个分形的程序收获

还是蛮多的,在做树形分形的时候自己自学了二叉树,顺便学会了二叉树的排序方法嘿嘿,发现原来树

这种原来在我看来很神秘,很复杂的数据结构也不过如此吗!
 
 
 

资源下载链接为: https://pan.quark.cn/s/d9ef5828b597 四路20秒声光显示计分抢答器Multisim14仿真源文件+设计文档资料摘要 数字抢答器由主体电路与扩展电路组成。优先编码电路、锁存器、译码电路将参赛队的输入信号在显示器上输出;用控制电路和主持人开关启动报警电路,以上两部分组成主体电路。通过定时电路和译码电路将秒脉冲产生的信号在显示器上输出实现计时功能,构成扩展电路。经过布线、焊接、调试等工作后数字抢答器成形。关键字:开关阵列电路;触发锁存电路;解锁电路;编码电路;显示电路 一、设计目的 本设计是利用已学过的数电知识,设计的4人抢答器。(1)重温自己已学过的数电知识;(2)掌握数字集成电路的设计方法和原理;(3)通过完成该设计任务掌握实际问题的逻辑分析,学会对实际问题进行逻辑状态分配、化简;(4)掌握数字电路各部分电路与总体电路的设计、调试、模拟仿真方法。 二、整体设计 (一)设计任务与要求: 抢答器同时供4名选手或4个代表队比赛,分别用4个按钮S0 ~ S3表示。 设置一个系统清除和抢答控制开关S,该开关由主持人控制。 抢答器具有锁存与显示功能。即选手按动按钮,锁存相应的编号,并在LED数码管上显示,同时扬声器发出报警声响提示。选手抢答实行优先锁存,优先抢答选手的编号一直保持到主持人将系统清除为止。 参赛选手在设定的时间内进行抢答,抢答有效,定时器停止工作,显示器上显示选手的编号和抢答的时间,并保持到主持人将系统清除为止。 如果定时时间已到,无人抢答,本次抢答无效。 (二)设计原理与参考电路 抢答器的组成框图如下图所示。它主要由开关阵列电路、触发锁存电路、解锁电路、编码电路和显示电路等几部分组成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值