文章首发于我的知乎专栏,原地址: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. C#------SortedLIst键值对的使用方法

    方法: SortedList sf = new SortedList(); sf.Add(, "广州"); sf.Add(, "江门"); sf.Add(, & ...

  2. POJ 2567 Code the Tree &amp; POJ 2568 Decode the Tree Prufer序列

    题目大意:2567是给出一棵树,让你求出它的Prufer序列.2568时给出一个Prufer序列,求出这个树. 思路:首先要知道Prufer序列.对于随意一个无根树,每次去掉一个编号最小的叶子节点,并 ...

  3. Python Scrapy 自动爬虫注意细节(3)

    一.对指定页面爬取 yield Request(url, meta={'cookiejar': response.meta['cookiejar']}, callback=self.parse_url ...

  4. Splash界面完美实现

    Flash闪烁界面的实现原理 1.首先 new一个数组里面放一些Random图片 private int[] drawables = new int[]{R.drawable.a,R.adable.b ...

  5. 【BZOJ3781、2038】莫队算法2水题

    [BZOJ3781]小B的询问 题意:有一个序列,包含N个1~K之间的整数.他一共有M个询问,每个询问给定一个区间[L..R],求Sigma(c(i)^2)的值,其中i的值从1到K,其中c(i)表示数 ...

  6. struts2中s:iterator 标签的使用详解 及 OGNL用法

    简单的demo: s:iterator 标签有3个属性:value:被迭代的集合id   :指定集合里面的元素的idstatus 迭代元素的索引 1:jsp页面定义元素写法 数组或list <s ...

  7. LeetCode-Water and Jug Problem

    You are given two jugs with capacities x and y litres. There is an infinite amount of water supply a ...

  8. js apply 引申

    apply 可以接受两个参数, fun.apply(thisArg[, argsArray]) 其中第二个参数是数组或类数组对象,所以有时传 arguments 也很正常,但是,认真的说,我测试出来: ...

  9. c#获取QQ音乐当前播放的歌曲名

    在网上找了很久,没找到方法,自己尝试着做,还是做出来了,很简单,就几句代码. Process[] ps = Process.GetProcessesByName("QQmusic" ...

  10. Storm-源码分析- metric

    首先定义一系列metric相关的interface, IMetric, IReducer, ICombiner (backtype.storm.metric.api) 在task中, 创建一系列bui ...