递归的逻辑(4)——递归与分形

  《最强大脑》第四季的一期节目中,挑战者余彬晶挑战的项目是“分形之美”。这是一个数学推理项目,章子怡女神和不懂球的胖子都一脸迷茫。

分形的概念

  分形(Fractal)一词,是曼德布罗特创造出来的,其原意具有不规则、支离破碎等意义,分形几何学是一门以非规则几何形态为研究对象的几何学。由于不规则现象在自然界是普遍存在的,因此分形几何又称为描述大自然的几何学。

  分形通常被定义为“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状,即具有自相似的性质。”

  分形图无处不在,绵延的海岸线,从远距离观察,其形状是极不规则的,但是从近距离观察,其局部形状又和整体形态相似;一颗参天大树,它的每一片叶子和枝干,都和主枝显现出了高度的相似性;一片雪花的每片花瓣都在放大后都显出了原图案惊人的相似性。

  既然分形图的每一部分都是整体的缩小,那么很自然地会联想到自身调用自身的程序。我们尝试用递归去绘制一颗分形树,下图是绘制的目标:

极坐标系下的向量旋转

  虽然绘制的目标是二维图形,但是比起刻度尺来,树的坐标关系要复杂的多,继续在直角坐标系下处理就显得有点笨拙了,此时不妨试试极坐标。

  极坐标用向量的长度和角度来表示坐标中的点,一个典型的极坐标如下:

  r是向量的长度,φ是向量与x轴逆时针方向的夹角,点t可以用(r,φ)来表示。可以看出,极坐标系仍未脱离原来的直角坐标系,仅仅是将直角坐标系上的点换了一种表示法,如果将点t 转换成直角坐标系的表示法,那么:

  这也是极坐标到直角坐标的转换公式。

  接下来将向量逆时针旋转θ,得到新的t’点:

  t’点的坐标:

  树的主干垂直于x轴,简单起见,可以让它附着在y轴上,这相当于φ等于90°:

  接下来,把一个稍短的向量r’向左旋转θ角得到点B,向右旋转-θ角得到点C:

  如此一来可以得到B和C的坐标:

  作为树的枝干,还需要将两个新向量上移,让它们以A为起点:

  现在得到了树的两个枝干以及枝干端点的坐标:

  继续按照上述方法进行下去将会得到更多的枝干:

编写代码

  知道原理后就可以编写相关代码:

 1 class Fractal:
 2     def __init__(self, fai, theta, depth):
 3         ‘‘‘
 4             分形图的基础形状
 5             Attributes:
 6                 fai:        向量的初始角度
 7                 theta:      向量每次逆时针旋转的角
 8                 depth:      树的深度, 当depth == 0时停止分形
 9         ‘‘‘
10         self.fai = fai
11         self.theta = theta
12         self.depth = depth
13
14     def draw(self, ax):
15         def draw_line(x1, y1, fai, depth, ax):
16             if depth == 0:
17                 return
18             # 由于np.cos和np.sin使用的参数是弧度,所以需要先把角度转换成弧度
19             radian = np.radians(fai)
20             # 旋转后的坐标
21             x2 = x1 + np.cos(radian) * depth
22             y2 = y1 + np.sin(radian) * depth
23             ax.plot([x1, x2], [y1, y2], color=‘g‘)
24             draw_line(x2, y2, fai + self.theta, depth - 1, ax)
25             draw_line(x2, y2, fai - self.theta, depth - 1, ax)
26         draw_line(0, 0, self.fai, self.depth, ax)
27
28 if __name__ == ‘__main__‘:
29     fig, ax = plt.subplots()
30     plt.axis(‘off‘)
31     plt.axis(‘equal‘)
32     fractal = Fractal(90, 30, 8)
33     fractal.draw(ax)
34     plt.show()

  我们用数的深度表示向量的长度,每一层枝干的长度都比上一层少1,直到深度是0为止。每一层枝干都是由上一层枝干的终点坐标、偏斜角和枝干长度决定的。fai的初始值是90°,用draw_line(0, 0, 90, depth)来画树的主干,这样才能保证主干是附着在y轴上,主干的终点坐标:

弧度和角度的转换公式是1rad=180°/π,π是无限不循环小散,因此python中这个转换是不精确的。如果直接运行np.cos(np.radians(90)),不会得到0,而是得到6.123233995736766e-17,这是一个相当小的数,可以当作0处理。

  运行结果如下:

  可以通过改变初始深度来观察树的分型过程:

if __name__ == ‘__main__‘:
    # 观察分形过程
    fig = plt.figure()
    for i in range(1, 9):
        ax = fig.add_subplot(3, 3, i)
        fractal = Fractal(90, 30, i)
        plt.axis(‘off‘)
        plt.axis(‘equal‘)
        plt.title(‘depth=‘ + str(i))
        fractal.draw(ax)
    plt.show()

  在绘制depth =1的分形树时,代码中plt.axis(‘equal‘)这句话是必须的,它将使x轴和y轴的定标系数相同,即单位长度相同。如果将其注释掉,由于角度和弧度间的不精确转换,将得到一条斜线:

  斜线是根据(0,0)和(6e-17, 1)绘制的。在添上plt.axis(‘equal‘)后,这个微小的斜率就可以忽略不计了:

  通过改变旋转角,可以得到一些有趣的分形:

  



   作者:我是8位的

  出处:http://www.cnblogs.com/bigmonkey

  本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途!

  扫描二维码关注公众号“我是8位的”

原文地址:https://www.cnblogs.com/bigmonkey/p/10350814.html

时间: 2024-11-05 11:51:35

递归的逻辑(4)——递归与分形的相关文章

递归的逻辑(3)——递归与分治

递归和分治天生就是一对好朋友.所谓分治,顾名思义,就是分而治之,是一种相当古老的方法. 在遥远的周朝,人们受生产力水平所限,无法管理庞大的土地和众多的人民,因此采用了封邦建国的封建制度,把土地一层一层划分下去,以达到分而治之的目的,这也许是最古老的分治法了: 分治的步骤 正像分封土地一样,分治法的目的就是为了把无法解决的大问题分解成若干个能够解决小问题.通常来说,分治法可以归纳为三个步骤: 1. 分解,将原问题分解成若干个与原问题结构相同但规模较小的子问题: 2. 解决,解决这些子问题.如果子问

3.sql中的向上递归和向下递归

1.向下递归 select * from table_name where 条件 connect by prior bmbm(本级关联条件)=sjbmbm(上级关联条件) start with bmbm(本级关联条件)='610000000000'(本级编码)--包含本级 select * from table_name where 条件 connect by prior bmbm(本级关联条件)=sjbmbm(上级关联条件) start with sjbmbm(本级关联条件)='6100000

二叉树的递归遍历和非递归遍历(附详细例子)

mnesia在频繁操作数据的过程可能会报错:** WARNING ** Mnesia is overloaded: {dump_log, write_threshold},可以看出,mnesia应该是过载了.这个警告在mnesia dump操作会发生这个问题,表类型为disc_only_copies .disc_copies都可能会发生. 如何重现这个问题,例子的场景是多个进程同时在不断地mnesia:dirty_write/2 mnesia过载分析 1.抛出警告是在mnesia 增加dump

递归逆转链表和递归合并有序链表的代码

#include <stdio.h> struct node { int val; struct node *pNext; }; struct node *gen() { struct node *pHead = NULL; for(int i = 10; i > 0; i--){ struct node * p = malloc(sizeof(struct node)); p -> val = i; p -> pNext = pHead; pHead = p; } retu

sqlserver 树结构递归(向上递归和向下递归)

--获取当前及以下部门 Create proc GetCurrentAndUnderOrg @orgId int as begin WITH cte AS ( SELECT * ,0 AS level FROM Static_Organ WHERE [email protected] UNION ALL SELECT g.*,level+1 FROM Static_Organ g INNER JOIN cte ON g.ParentOrgan=cte.OrganID ) SELECT * FRO

二叉树的非递归遍历(借鉴递归思想实现非递归遍历)

1 // 树结点定义 2 typedef struct TNode 3 { 4 int value; 5 TNode *left; 6 TNode *right; 7 }*PTNode; 1. 前序遍历的非递归实现(借鉴递归思想实现) 思想: 访问到一结点时,先将其入栈,假设入栈节点为P. 访问P,将P的右孩子和左孩子依次入栈,这样就保证了每次左孩子在右孩子前面被访问. 1 void preOrderNoneRecursion(PTNode root) 2 { 3 if(root == NULL

【C语言】求斐波那契(Fibonacci)数列通项(递归法、非递归法)

意大利的数学家列昂那多·斐波那契在1202年研究兔子产崽问题时发现了此数列.设一对大兔子每月生一对小兔子,每对新生兔在出生一个月后又下崽,假若兔子都不死亡.   问:一对兔子,一年能繁殖成多少对兔子?题中本质上有两类兔子:一类是能生殖的兔子,简称为大兔子:新生的兔子不能生殖,简称为小兔子:小兔子一个月就长成大兔子.求的是大兔子与小兔子的总和. 月     份  ⅠⅡ  Ⅲ  Ⅳ  Ⅴ Ⅵ  Ⅶ  Ⅷ Ⅸ Ⅹ  Ⅺ  Ⅻ大兔对数 1  1   2   3   5  8  13  21 34 55 

递归如何转换为非递归

递归算法实际上是一种分而治之的方法,它把复杂问题分解为简单问题来求解.递归的特点包括:递归过程简洁.易编.易懂:递归过程效率低.重复计算多. 考虑递归的执行效率低,可以尝试将递归过程转换为非递归过程.本文就是来探讨怎么转换的. 将递归算法转换为非递归算法有两种方法,一种是直接求值(迭代/循环),不需要回溯:另一种是不能直接求值,需要回溯.前者使用一些变量保存中间结果,称为直接转换法:后者使用栈保存中间结果,称为间接转换法,下面分别讨论这两种方法. 一.直接转换法 直接转换法通常用来消除尾递归和单

二叉树的遍历--递归实现与非递归实现

二叉树的表示 在研究二叉树的遍历之前,我们需要先看看二叉树的表示方式. 一般来说,我们使用自定义的数据结构或是数组来表示二叉树. 二叉树的数据结构: public class TreeNode { public int val; // 左孩子 public TreeNode left; // 右孩子 public TreeNode right; } 数组形式表现二叉树 当我们使用数组形式表现二叉树时,我们将数组第一个节点的索引置为「1」,也就是根节点,如果我们通用性的将其当为「x」,那么它的左孩