文章首发于我的知乎专栏,原地址:https://zhuanlan.zhihu.com/p/26606208

以前看到过一个问题:谢尔宾斯基三角形能用编程写出来么?该怎么写? - 知乎,在回答里,各方大神用各种语言各种方法实现了一遍,非常精彩。我当时也回答了这个问题,是用H5的Canvas实现的。这在前端技术上没什么难度,主要是算法比较有可玩性,所以当时就手痒了。

谢尔宾斯基三角形是分形图形的一种,大概很多人第一次见到它都是在中学教科书上。它长这样:

我用了两种方法构造它:直接绘制三角形和间接用折线逼近。

1.直接绘制三角形。具体方法就是先画一个大三角形,再递归绘制一堆倒三角形。这种方法最为直观也最好想。放码过来:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sierpinski Triangle</title>
</head>
<body>
<canvas id="canvas"
width="600" height="600"
style="display:block;margin:50px auto">
你的浏览器不支持canvas
</canvas>
</body>
<script type="text/javascript">
var context =
document.getElementById("canvas")
.getContext("2d"); //根据三顶点坐标绘制一个三角形
function triangle(p1,p2,p3){
context.moveTo(p1.x,p1.y);
context.lineTo(p2.x,p2.y);
context.lineTo(p3.x,p3.y);
context.lineTo(p1.x,p1.y);
} /*绘制谢尔宾斯基三角形的方法
p:正三角形中心点坐标,len:三角形边长*/
function SierpinskiTriangle(p,len){
var r=len/Math.sqrt(3);
//计算顶点坐标
var p1={x:p.x, y:p.y-r};
var p2={x:p.x-len/2, y:p.y+r/2};
var p3={x:p2.x+len, y:p2.y};
triangle(p1,p2,p3); //绘制正三角形外框
//递归绘制所有的倒三角形
middleTriangle(p1,p2,p3); function middleTriangle(p1,p2,p3){
var tp1={x:(p2.x+p3.x)/2, y:(p2.y+p3.y)/2};
var tp2={x:(p1.x+p3.x)/2, y:(p1.y+p3.y)/2};
var tp3={x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2};
triangle(tp1,tp2,tp3);
//递归前判断最短线条长度是否短于临界值
if((tp1.x-tp2.x)*(tp1.x-tp2.x)+
(tp1.y-tp2.y)*(tp1.y-tp2.y)>20){
middleTriangle(p1,tp2,tp3);
middleTriangle(p2,tp1,tp3);
middleTriangle(p3,tp1,tp2);
}
}
} //绘制
SierpinskiTriangle({x:300,y:360},560);
context.lineWidth = 0.5;
context.strokeStyle = "#F5270B";
context.stroke();
</script>
</html>

保存成html文件用浏览器打开,效果如下:

2.折线逼近法:

这个方法比较神奇,简单来说,是从一条水平线开始,每次递归都把所有线段替换成有规律的三段折线。无限递归下去,整段折线会越来越逼近谢尔宾斯基三角形。

用这个思路实现的SierpinskiTriangle函数如下,多了一个设置递归深度的depth参数:

/*绘制谢尔宾斯基三角形的方法
p:正三角形中心点坐标,len:三角形边长,depth:递归深度*/
function SierpinskiTriangle(p,len,depth){
var r=len/Math.sqrt(3);
//记录当前端点,默认为左下角顶点坐标
var currentPoint={x:p.x-len/2, y:p.y+r/2};
//记录当前方向角
var currentAngle=0; //旋转方法,将下次画线的方向逆时针旋转
function turn(angle){
currentAngle+=angle;
}
//画线方法,根据当前端点和画线方向绘制
function draw_line(length){
var angle=currentAngle/180*Math.PI;
currentPoint.x+=length*Math.cos(angle);
currentPoint.y-=length*Math.sin(angle);
context.lineTo(currentPoint.x,currentPoint.y);
} //开始画折线,如果深度是偶数便可直接绘制折线,否则需要以斜60度为初始方向
context.moveTo(currentPoint.x,currentPoint.y);
if (depth%2==0){
curve(depth,len,-60);
}else{
turn(60);
curve(depth,len,-60);
} function curve(order,length,angle)
{
if (order==0){
draw_line(length);
}else{
//递归画三段折线
curve(order-1,length/2,-angle);
turn(angle);
curve(order-1,length/2,angle);
turn(angle);
curve(order-1,length/2,-angle);
}
}
}

用这个函数替换上一段HTML中的SierpinskiTriangle函数就行了,调用时需要给depth参数赋值,推荐为9。我将depth从0到9十种情况的曲线放在同一个canvas里输出了,可供直观理解:

参考来源:
Sierpinski triangle
Sierpiński arrowhead curve

小练手:用HTML5 Canvas绘制谢尔宾斯基三角形的更多相关文章

  1. Python使用递归绘制谢尔宾斯基三角形

    谢尔宾斯基三角形使用了三路递归算法,从一个大三角形开始,通过连接每一个边的中点,将大三角型分为四个三角形,然后忽略中间的三角形,依次对其余三个三角形执行上述操作. 运行效果: 源代码: 1 impor ...

  2. python 使用turtule绘制递归图形(螺旋、二叉树、谢尔宾斯基三角形)

    插图工具使用Python内置的turtle模块,为什么叫这个turtle乌龟这个名字呢,可以这样理解,创建一个乌龟,乌龟能前进.后退.左转.右转,乌龟的尾巴朝下,它移动时就会画一条线.并且为了增加乌龟 ...

  3. python---使用递归实现谢尔宾斯基三角形及汉诺塔

    渐入佳境. # coding: utf-8 import turtle ''' # =================turtle练手== def draw_spiral(my_turtle, lin ...

  4. 分形之谢尔宾斯基(Sierpinski)三角形

    谢尔宾斯基三角形(英语:Sierpinski triangle)是一种分形,由波兰数学家谢尔宾斯基在1915年提出,它是一种典型的自相似集.也有的资料将其称之为谢尔宾斯基坟垛. 其生成过程为: 取一个 ...

  5. 分形之谢尔宾斯基(Sierpinski)地毯

    前面讲了谢尔宾斯基三角形,和这一节的将把三角形变为正方形,即谢尔宾斯基地毯,它是由瓦茨瓦夫·谢尔宾斯基于1916年提出的一种分形,是自相似集的一种. 谢尔宾斯基地毯的构造与谢尔宾斯基三角形相似,区别仅 ...

  6. 分形之谢尔宾斯基(Sierpinski)四面体

    前面讲了谢尔宾斯基三角形,这一节的将对二维三角形扩展到三维,变成四面体.即将一个正四面体不停地拆分,每个正四面体可以拆分成四个小号的正四面体.由二维转变到三维实现起来麻烦了许多.三维的谢尔宾斯基四面体 ...

  7. 混沌分形之谢尔宾斯基(Sierpinski)

    本文以使用混沌方法生成若干种谢尔宾斯基相关的分形图形. (1)谢尔宾斯基三角形 给三角形的3个顶点,和一个当前点,然后以以下的方式进行迭代处理: a.随机选择三角形的某一个顶点,计算出它与当前点的中点 ...

  8. 【数据结构与算法Python版学习笔记】递归(Recursion)——定义及应用:分形树、谢尔宾斯基三角、汉诺塔、迷宫

    定义 递归是一种解决问题的方法,它把一个问题分解为越来越小的子问题,直到问题的规模小到可以被很简单直接解决. 通常为了达到分解问题的效果,递归过程中要引入一个调用自身的函数. 举例 数列求和 def ...

  9. html5 Canvas绘制图形入门详解

    html5,这个应该就不需要多作介绍了,只要是开发人员应该都不会陌生.html5是「新兴」的网页技术标准,目前,除IE8及其以下版本的IE浏览器之外,几乎所有主流浏览器(FireFox.Chrome. ...

随机推荐

  1. iOS开发之-- textview 光标起始位置偏移

    使用textview的时候,会发生光标偏移的情况,其实是因为iOS7里导航栏,状态栏等有个边缘延伸的效果在. 把边缘延伸关掉就好了.代码如下 //取消iOS7的边缘延伸效果(例如导航栏,状态栏等等) ...

  2. std::condition_variable(二)

    #include <iostream> // std::cout #include <thread> // std::thread, std::this_thread::yie ...

  3. oracle导入导出 dmp文件

    oracle导入导出 dmp文件: 打开cmd窗口,在cmd窗口下,按照个人需要输入以下对应的命令: 1.imp 用户名/密码@网络服务名 file=XXX.dmp fromuser=XXX tous ...

  4. jquery页面滚动显示浮动菜单栏锚点定位效果

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  5. 更新openssl

    在安装nodejs或者nginx什么的时候,有时候会报如下错误 npm: relocation error: npm: symbol SSL_set_cert_cb, version libssl.s ...

  6. PHP 创建中文目录的情况

    因为一个作业需要创建一些中文的目录,其实主要还是考虑一下编码问题. 首先确认下系统环境是什么编码,如果是gbk或者GB2312那就需要转下码,还有些特殊字符,就需要有个特殊的写法. iconv('UT ...

  7. android开发笔记(二)导入项目到eclipse和另一个项目

    NND,eclipse里导入工程出现问题了,整了半天,来个这问题,无效工程描述,找了半天看.projec文件是否工程名对应,看androidManifest.XML换里面的代码版本号,我擦都无济于事. ...

  8. 剑指Offer——二叉搜索树的后序遍历序列

    题目描述: 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 分析: 二叉查找树(Binary Search ...

  9. 获取当前文件夹以及子文件夹下所有文件C++

    void getFiles( string path,vector<string>& files) { //文件句柄 ; //文件信息 struct _finddata_t fil ...

  10. MySQL优化(一):MySQL分库分表

    一.分库分表种类 1.垂直拆分 在考虑数据拆分的时候,一般情况下,应该先考虑垂直拆分.垂直可以理解为分出来的库表结构是互相独立各不相同的. - 如果有多个业务,每个业务直接关联性不大,那么就可以把每个 ...