謝爾賓斯基三角形能用編程寫出來么?該怎麼寫?


來個Haskell版本, 這個版本的特點是, 不需要開一個 n^2 的數組來表示畫布的狀態, 而是通過對 "圖形" 做抽象, 並且實現幾個基本的 圖形上的 "組合運算", 最後通過圖形的組合運算得到結果. 因此寫這個代碼的過程中, 沒有太多的trick (不需要關心數組越界, 沒有什麼magic number).

另外由於圖形的每個具體像素的值都是在輸出的時候才產生 (通過 draw 這個函數), 所以相較於其它答主開一個n方的canvas數組來保存圖形的 push 的方式來說, 這裡用的是一種 pull 的模式. 因為用的是這種pull的模式, 我們能表達出無限大的圖形, 而把所有具體的計算都留在了圖形輸出的時候.
也因為這種pull的方式, 這段代碼的空間複雜度比較低 (這裡對空間的需求取決於函數調用棧的深度, 空間複雜度為 O(log n)), 而時間複雜度則較高 (為 O(n^2 * log n)), 其中 n 為三角形的邊長.

完整代碼如下:

import Control.Monad

vecAdd (i0, j0) (i1, j1) = (i0 + i1, j0 + j1)
vecSub (i0, j0) (i1, j1) = (i0 - i1, j0 - j1)
inRect ((r, c), (rn, cn)) (pr, pc) = pr &>= r pr &< r + rn pc &>= c pc &< c + cn move vec g = g . (`vecSub` vec) inset rect g0 g1 = p -&> if inRect rect p then g0 p else g1 p
draw ((r, c), (rn, cn)) g = do
forM_ [r..r+rn-1] $
-&> do
forM_ [c..c+cn-1] $ c -&> do
putStr $ g (r, c)
putStr "
"

simpleTriangle = ((r, c) -&> if (r, c) == (0, 0) then "△" else if (r, c) == (0, 1) then "" else " ")

sierpinskiTriangle 0 = simpleTriangle
sierpinskiTriangle n = let
t = sierpinskiTriangle (n-1)
h = 2 ^ (n-1)
sz = (h, 2*h)
lpos = (h, 0)
rpos = (h, 2*h)
in inset (lpos, sz) (move lpos t) . inset (rpos, sz) (move rpos t) $ move (0, h) t

drawTriangle n = draw ((0, 0), (2^n, 2^(n+1))) $ sierpinskiTriangle n

--- play ground ---

dot (r, c) = if (r, c) == (0, 0) then "X" else " "
d = draw ((0, 0), (10, 10))
t0 = simpleTriangle
t = sierpinskiTriangle
dt = drawTriangle

main = do
forM_ [0..5] $
-&> do
putStrLn $ "
Sierpinski Triangle " ++ show n ++ ":"
drawTriangle n

運行效果:

稍微說一下:

這裡做了幾個抽象:

1. 點 和 向量: type Point = (Int, Int), type Vec = (Int, Int); 為了簡單直接用tuple表示點和向量.
2. 矩形: type Rect = (Point, Vec); 這裡用矩形的左上角坐標表示位置, 用左上角到右下角的向量表示矩形的尺寸.
3. 圖形: type Graph = Point -&> Pixel; 注意這裡把圖形定義為了一個 "點到像素的函數".
4. 像素: type Pixel = String; 因為是在控制台上畫圖, 所以把像素定義為字元串.

然後基於上述抽象, 定義了幾個圖形上的運算:

1.. 圖形上的平移運算: move :: Vec -&> Graph -&> Graph , 即把給定圖形按照給定向量移動, 得到新圖形
2. 圖形上的嵌入運算: inset :: Rect -&> Graph -&> Graph -&> Graph, 即把給定圖形的指定部分嵌入到另一個圖形中, 返回新的圖形
3. 圖形的繪製: draw :: Rect -&> Graph -&> IO () , 即繪製給定圖形的給定區域

然後核心代碼就是下面這幾行:

sierpinskiTriangle 0 = simpleTriangle
sierpinskiTriangle n = let
t = sierpinskiTriangle (n-1)
h = 2 ^ (n-1)
sz = (h, 2*h)
lpos = (h, 0)
rpos = (h, 2*h)
in inset (lpos, sz) (move lpos t) . inset (rpos, sz) (move rpos t) $ move (0, h) t

其中第一行就是說, 0階sierpinskiTriangle就是一個simpleTriangle.
第二行及後面就是計算了一些參數, 最後返回一個圖形的表達式:

inset (lpos, sz) (move lpos t) . inset (rpos, sz) (move rpos t) $ move (0, h) t

其中 t 是 n-1 階的 sierpinskiTriangle,
h 是 n-1 階 sierpinskiTriangle 的高度,
sz 是 n-1 階 sierpinskiTriangle 的尺寸, 這裡用一個圖形的BBox左上角到右下角的向量表示.
lpos 和 rpos 分別是左下角三角形 和 右下角三角形的位置

整句話就是說, 在 最上方的 n-1 階 sierpinskiTriangle 上嵌入右下角的 n-1 階 sierpinskiTriangle, 然後嵌入左下角的 n-1 階 sierpinskiTriangle . 然後就得到 n 階 sierpinskiTriangle 了.


Mathematica:

Graphics@Nest[Scale[#,0.5,{0,0}]~Translate~CirclePoints[0.5,3],
Triangle@CirclePoints[3],6]

Graphics@Point@FoldList[+##/2,{0,0},N@CirclePoints[3]~RandomChoice~1*^4]

ArrayPlot[CellularAutomaton[22,{{1},0},2^8],AspectRatio-&>0.87]

Nest[Subsuperscript[#,#,#],o,6]



@羅宸的Haskell實現很有意思,但是代碼對初學者來說還是相對難理解了些。
我這裡恰好有一年多前用Haskell寫的畫sierpinski三角形的代碼,也來湊個熱鬧。

infixl 5 ===
(===) = (++)

infixl 5 |||
(|||) = zipWith (++)

center n = map ((spaces ++) . (++ spaces))
where spaces = replicate (2 ^ (n - 1)) " "

-- | Impletement print function in normal recursive form with new operators
sierpinskiS :: Int -&> [[Char]]
sierpinskiS 0 = ["△"]
sierpinskiS n = center n s
===
(s ||| s)
where s = sierpinskiS (n - 1)

main = mapM_ putStrLn $ sierpinskiS 4

輸出結果如下:


△ △
△ △
△ △ △ △
△ △
△ △ △ △
△ △ △ △
△ △ △ △ △ △ △ △
△ △
△ △ △ △
△ △ △ △
△ △ △ △ △ △ △ △
△ △ △ △
△ △ △ △ △ △ △ △
△ △ △ △ △ △ △ △
△ △ △ △ △ △ △ △ △ △ △ △ △ △ △ △

可以看到上面的代碼比@羅宸的簡潔不少,相對要直觀些。如果用vector代替String類型,性能也會高不少。

不過當初寫這個代碼可不是為了這個簡單的目標,而是乘機將F-Alg和Free Monad都整了個遍,分別用這些高級技術實現了畫sierpinski三角形的函數。將這些東西都理解透了。

--------------- 我是分割線 -----------------------------------
下面是 @羅宸的代碼的更新版,主要是使用Applicative來組合siepinski三角形。

{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE FlexibleContexts #-}

import Control.Applicative

blank = const " "
triangle (0, 0) = "△ "
triangle _ = ""

addVec2 (r1, c1) (r2, c2) = (r1 + r2, c1 + c2)
subVec2 (r1, c1) (r2, c2) = (r1 - r2, c1 - c2)
inrect (xr, xc) ((r, c), (rn, cn)) = xr &>= r xr &< r + rn xc &>= c xc &< c + cn move vec g = g . (`subVec2` vec) draw ((r, c), (rn, cn)) g = do forM_ [r..r+rn-1] $ -&> do
forM_ [c..c+cn-1] $ c -&> do
putStr $ g (r, c)
putStr "
"

sierpinskiT 0 = triangle
sierpinskiT n = condImage &<$&> inTop n &<*&> move (0, h) t
&<*&> (condImage &<$&> inBotL n &<*&> move (h, 0) t
&<*&> (condImage &<$&> inBotR n &<*&> move (h, 2*h) t
&<*&> blank))
where
t = sierpinskiT (n-1)
inTop n p = p `inrect` ((0, h), (h, 2*h))
inBotL n p = p `inrect` ((h, 0), (h, 2*h))
inBotR n p = p `inrect` ((h, 2*h), (h, 2*h))
h = 2 ^ (n-1)

condImage cond t1 t2 = if cond then t1 else t2

drawTriangle n = draw ((0, 0), (2^n, 2^(n+1))) $ sierpinskiT n

再次改進後的代碼如下

import Data.Monoid

type Graph a = (Int, Int) -&> a
type Vec2 = (Int, Int)

newtype CharPixel = CharPixel { getStr :: String }
instance Monoid CharPixel where
mempty = CharPixel " "
CharPixel " " `mappend` CharPixel " " = CharPixel " "
CharPixel " " `mappend` CharPixel a = CharPixel a
CharPixel a `mappend` CharPixel b = CharPixel a

triangle (0, 0) = CharPixel "△"
triangle _ = CharPixel " "
-- triangle (0, 0) = Just "△"
-- triangle _ = Nothing

addVec2 (r1, c1) (r2, c2) = (r1 + r2, c1 + c2)
subVec2 (r1, c1) (r2, c2) = (r1 - r2, c1 - c2)
inrect (xr, xc) ((r, c), (rn, cn)) = xr &>= r xr &< r + rn xc &>= c xc &< c + cn move vec g = g . (`subVec2` vec) draw ((r, c), (rn, cn)) g = do forM_ [r..r+rn-1] $ -&> do
forM_ [c..c+cn-1] $ c -&> do
putStr $ g (r, c)
putStr "
"

sierpinskiT 0 p = triangle p
sierpinskiT n p = if p `inrect` ((0, 0), (h, w)) then
(centerT n t `above` (t `beside` t)) p
else
mempty
where
t = sierpinskiT (n-1)
above = aboveT n
beside = besideT n
h = 2 ^ n
w = 2 ^ (n+1)

aboveT :: Monoid a =&> Int -&> Graph a -&> Graph a -&> Graph a
aboveT n t1 t2 = t1 &<&> move (2 ^ (n-1), 0) t2

besideT :: Monoid a =&> Int -&> Graph a -&> Graph a -&> Graph a
besideT n t1 t2 = t1 &<&> move (0, 2 ^ n) t2

centerT n = move (0, 2 ^ (n-1))

drawTriangle n = draw ((0, 0), (2^n, 2^(n+1))) (getStr . sierpinskiT n)
-- drawTriangle n = draw ((0, 0), (2^n, 2^(n+1))) . fmap (maybe " " id) $ sierpinskiT n

運行後輸出結果同上。


可以從 Studio/FractalsVisualizationJ - J Wiki 里抄

f=: ,~,.~
f ^:3 ,1
1 1 1 1 1 1 1 1
1 0 1 0 1 0 1 0
1 1 0 0 1 1 0 0
1 0 0 0 1 0 0 0
1 1 1 1 0 0 0 0
1 0 1 0 0 0 0 0
1 1 0 0 0 0 0 0
1 0 0 0 0 0 0 0
" *" {~ f ^:3 ,1
********
* * * *
** **
* *
****
* *
**
*
" *" {~ f ^:2 ,1
****
* *
**
*
" *" {~ f ^:4 ,1
****************
* * * * * * * *
** ** ** **
* * * *
**** ****
* * * *
** **
* *
********
* * * *
** **
* *
****
* *
**
*


從Sierpinski三角的定義出發畫自然可以,但無論時空複雜度還是編程複雜度都太高了,其實Sierpinski三角形有個非常優美的位運算結論以及畫法
在屏幕上i行j列的像素,如果(ij)==j那就輸出這個像素,否則不輸出,這樣的圖形就是Sierpinski三角

int h = 30;
int w = 30;
for(int i = 0; i &< h; i++) { for(int j = 0; j &< w; j++) { printf("%c", ((ij)==j)?"#":" "); } puts(""); }

當然控制台輸出#號太丑了,我們可以用html5的canvas畫下來看效果!

&
&
&

&&

&

public class IFS
{
public static void main(String[] args)
{ // Plot T iterations of IFS on StdIn.
int T = Integer.parseInt(args[0]);
double[] dist = StdArrayIO.readDouble1D();
double[][] cx = StdArrayIO.readDouble2D();
double[][] cy = StdArrayIO.readDouble2D();
double x = 0.0, y = 0.0;
for (int t = 0; t &< T; t++)
{ // Plot 1 iteration.
int r = StdRandom.discrete(dist);
double x0 = cx[r][0]*x + cx[r][1]*y + cx[r][2];
double y0 = cy[r][0]*x + cy[r][1]*y + cy[r][2];
x = x0;
y = y0;
StdDraw.point(x, y);
}
StdDraw.save("result.png");
System.out.println("Done.");
}
}

其中 StdDraw 是畫圖的標準庫。

執行的時候需要外部文件 sierpinski.txt,內容如下:
3
.33 .33 .34
3 3
.50 .00 .00
.50 .00 .50
.50 .00 .25
3 3
.00 .50 .00
.00 .50 .00
.00 .50 .433

各參數說明如下:
1)第一行表示樣本的個數,(3)表示三個點裡面按一定概率選取;
2)第二行表示相應的概率 ,(1/3 1/3 1/3)表示等可能地選取三個點;
3)後面兩段表示矩陣的行數(和樣本的個數一致)、列數(列數都是3,表示根據做線性組合的係數,因為是二維平面,所以只有 x y 1 這三項的係數),以及對應於樣本中每一個點,做線性組合時取的係數。

運行 java IFS 10000 &< sierpinski.txt ,其中 10000 表示循環 10000 次,得到結果如下圖:

循環 50000 次:

循環 80000 次:

類似地還可以實現很多其他的分形圖案:

Barnsley 蕨:

樹:

珊瑚:

以上。

參考書目:
Robert Sedgewick Kevin Wayne, Introduction to Programming in Java -- An Interdisciplinary Approach, pp. 231-235, Section 2.2, Program 2.2.3. 可參看:Libraries and Clients


手癢寫個html5 canvas的版本……用了兩種方法:直接繪製三角形和間接用折線逼近。放碼過來:

1.遞歸繪製倒三角形,也是輪子哥 @vczh 提到的最直接的思路

&
&
&
&
&Sierpinski Triangle&
&

&
&
你的瀏覽器不支持canvas
&
&

&

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);
}
}
}

雖然用setInterval函數可以做出遞歸深度遞增時的不同模擬效果的簡單動畫,不過逼乎不支持動圖,我將depth從0到9十種情況的曲線放在同一個canvas里輸出了,可供觀察:

參考來源:
Sierpinski triangle
Sierpiński arrowhead curve


// Sierpinski Triangle (https://en.wikipedia.org/wiki/Sierpinski_triangle) - Shader 2015 by JT
// shortest codegolfed 127char version by FabriceNeyret2
void mainImage( out vec4 o, vec2 I )
{
I /= iResolution.xy;
o -= o;
I.x-=.5*I.y;
for(int i = 0; i &< 9; i++) I = 2.*fract(I), I.x&>=1.I.y&>=1. ? o++ : o;
}

127個位元組的代碼,生成Sierpinski三角形.這是我見到的最精減的生成方式,見:https://www.shadertoy.com/view/Ms33WH


可以。


除了各位聚聚提到的使用遞歸來不斷細分三角形的方法,其實還可以嘗試元胞自動機:
https://en.m.wikipedia.org/wiki/Elementary_cellular_automaton

對應題目要求,這裡應用 Rule 90 即可,效果如下:

這種方法實際上是通過保留一維元胞自動機的歷史狀態來表達的,可以對照這個鏈接來想像一下每一步的狀態:http://mathworld.wolfram.com/Rule90.html

實際上Elementry Cellular Automata是一個非常有意思的話題,想深入了解的話可以去看Stephen Wolfram的《A New Kind of Science》(https://www.wolframscience.com/)。

至於具體的編程實現,對於這種無窮遞歸的問題你會發現一門支持Lazy Evaluation的語言很好用。為什麼不試試Haskell呢?(逃


《Visual Basic 高級圖形程序設計教程 (兼容VB4.0!)》裡面有大量的這些例子,總的來說就是先畫一個三角形,然後畫一堆倒三角。


結論寫在前面,使用自動機的話,rule 18, 22, 26, 60, 82, 90, 102, 126, 146, 154, 210, 218 都可以畫出謝爾賓斯基三角形。

====

來個python的啦。

naive版:(106個字元)

print (lambda n:"
".join(["".join(["*"if(ij)==j else" "for j in range(2**n)])for i in range(2**n)]))(4)

====

下面是各種自動機版本

rule218:

(lambda n:(lambda sn:(lambda f:(lambda w:w(w))(lambda c:f(lambda *x:c(c)(*x))))(lambda fun:lambda rule,seq,time:fun(rule,(lambda num, seq:(lambda re:(__import__("__builtin__").__dict__["print"](seq),re)[1])("".join(map((lambda s:(lambda s:((8-len(s))*"0"+s)[::-1])(bin(num)[2:])[int(s, 2)]),(lambda pseq:[pseq[i:i+3] for i in xrange(len(pseq)-2)])("0"+seq.replace("*","1").replace(" ","0")+"0"))).replace("1","*").replace("0"," ")))(rule,seq),time-1) if time &> 0 else None)(218,(sn-1)*" "+"*"+(sn-1)*" ",sn))(2**n))(4)

rule18:

(lambda n:(lambda sn:(lambda f:(lambda w:w(w))(lambda c:f(lambda *x:c(c)(*x))))(lambda fun:lambda rule,seq,time:fun(rule,(lambda num, seq:(lambda re:(__import__("__builtin__").__dict__["print"](seq),re)[1])("".join(map((lambda s:(lambda s:((8-len(s))*"0"+s)[::-1])(bin(num)[2:])[int(s, 2)]),(lambda pseq:[pseq[i:i+3] for i in xrange(len(pseq)-2)])("0"+seq.replace("*","1").replace(" ","0")+"0"))).replace("1","*").replace("0"," ")))(rule,seq),time-1) if time &> 0 else None)(90,(sn-1)*" "+"*"+(sn-1)*" ", sn))(2**n))(5)

這裡有個很有意思的現象就是,其實所有的自動機滿足

**01 *010

都是可以畫出這個圖形的,也就是說,rule 18, 26, 82, 90, 146, 154, 210, 218 都可以畫出這個圖形。

為什麼呢,很簡單,因為以中間一點產生的這個圖形里根本不會出現111,110,011這三種組合。。。

另外兩個不屬於這個系列的自動機是rule22跟rule126。

rule22:

(lambda n:(lambda sn:(lambda f:(lambda w:w(w))(lambda c:f(lambda *x:c(c)(*x))))(lambda fun:lambda rule,seq,time:fun(rule,(lambda num, seq:(lambda re:(__import__("__builtin__").__dict__["print"](seq),re)[1])("".join(map((lambda s:(lambda s:((8-len(s))*"0"+s)[::-1])(bin(num)[2:])[int(s, 2)]),(lambda pseq:[pseq[i:i+3] for i in xrange(len(pseq)-2)])("0"+seq.replace("*","1").replace(" ","0")+"0"))).replace("1","*").replace("0"," ")))(rule,seq),time-1) if time &> 0 else None)(22,(sn-1)*" "+"*"+(sn-1)*" ", sn))(2**n))(5)

rule126:

(lambda n:(lambda sn:(lambda f:(lambda w:w(w))(lambda c:f(lambda *x:c(c)(*x))))(lambda fun:lambda rule,seq,time:fun(rule,(lambda num, seq:(lambda re:(__import__("__builtin__").__dict__["print"](seq),re)[1])("".join(map((lambda s:(lambda s:((8-len(s))*"0"+s)[::-1])(bin(num)[2:])[int(s, 2)]),(lambda pseq:[pseq[i:i+3] for i in xrange(len(pseq)-2)])("0"+seq.replace("*","1").replace(" ","0")+"0"))).replace("1","*").replace("0"," ")))(rule,seq),time-1) if time &> 0 else None)(126,(sn-1)*" "+"*"+(sn-1)*" ", sn))(2**n))(5)

稍微飽滿一點點呢。

下面是兩個斜的,不好看w

rule60:

(lambda n:(lambda sn:(lambda f:(lambda w:w(w))(lambda c:f(lambda *x:c(c)(*x))))(lambda fun:lambda rule,seq,time:fun(rule,(lambda num, seq:(lambda re:(__import__("__builtin__").__dict__["print"](seq),re)[1])("".join(map((lambda s:(lambda s:((8-len(s))*"0"+s)[::-1])(bin(num)[2:])[int(s, 2)]),(lambda pseq:[pseq[i:i+3] for i in xrange(len(pseq)-2)])("0"+seq.replace("*","1").replace(" ","0")+"0"))).replace("1","*").replace("0"," ")))(rule,seq),time-1) if time &> 0 else None)(60,"*"+(sn-1)*" ", sn))(2**n))(5)

rule102:

(lambda n:(lambda sn:(lambda f:(lambda w:w(w))(lambda c:f(lambda *x:c(c)(*x))))(lambda fun:lambda rule,seq,time:fun(rule,(lambda num, seq:(lambda re:(__import__("__builtin__").__dict__["print"](seq),re)[1])("".join(map((lambda s:(lambda s:((8-len(s))*"0"+s)[::-1])(bin(num)[2:])[int(s, 2)]),(lambda pseq:[pseq[i:i+3] for i in xrange(len(pseq)-2)])("0"+seq.replace("*","1").replace(" ","0")+"0"))).replace("1","*").replace("0"," ")))(rule,seq),time-1) if time &> 0 else None)(102,(sn-1)*" "+"*", sn))(2**n))(4)


先畫三個三角形,然後於每個三角形里在遞歸的畫三個。

類似的可以搞四邊形。


大家都在說遞歸,我來補充一些數學知識吧,代碼很簡單,我寫幾行偽代碼。
可以迭代的。設三角形的頂點為A,B,C,
先取一個任意點X=X0.
for (int i=0;i&<1000000;i++){
D=ABC三個點中隨機取一個點
X=(X+D)/2 ,也就是取X和D點的中點
畫出X
}
好了,不停這樣迭代下去就可以了。
取中點就是橫坐標和縱坐標分別加起來除以二。


Mathematica:

Graphics[Polygon /@ #] /@
NestList[
Join @@ (Partition[Mean /@ Tuples[#, 2], 3] /@ #) ,
{CirclePoints@3},
5
]

在線運行,http://mmascript.com/kernel


無窮個程序會崩潰吧,如果輸出有限個我倒是有個思路,先繪製一個三角形,然後複製兩個分別沿兩條邊平移至線段末端組成一個內嵌倒三角的圖案,然後一直重複平移整個圖案的動作即可。目測幾十行代碼即可做到。


不知道為什麼這個問題總是不斷地出現在我的時間線里……………………………… 我只好答一下了。

貌似畫這玩意有兩種方法,一是一個像素一個像素的掃過去,每一個像素決定要不要塗黑,另一種是無腦地遞歸畫白色三角形。嗯,還有其他逼格特別高的方法我智商不高就不說了。我用的是醜陋慢速毫無逼格的遞歸畫白三角形方法,平台是同樣醜陋慢速毫無逼格的 Processing(工作機器上沒裝其他可以用來畫圖的玩意……)。畫出來以後才想起來尼瑪 Processing 的座標系是倒過來的…………………………………………

float Sqrt3 = sqrt(3.0);
float Unit = 1000;

void setup()
{
noStroke();
fill(0);
size(1000, 1000);
noLoop();
}

void iter(int level, int max_level, float start_x, float start_y)
{
if(level &> max_level)
return;
triangle(start_x + Unit / level / 2.0, start_y + Unit / level / 2.0 * Sqrt3,
start_x + Unit / level / 2.0 * 3.0, start_y + Unit / level / 2.0 * Sqrt3,
start_x + Unit / level, start_y);
iter(level*2, max_level, start_x + Unit / level / 2.0, start_y + Unit / level / 2.0 * Sqrt3);
if(level == 2)
return;
triangle(start_x - Unit / level / 2.0, start_y - Unit / level / 2.0 * Sqrt3,
start_x + Unit / level / 2.0, start_y - Unit / level / 2.0 * Sqrt3,
start_x, start_y - Unit / level * Sqrt3);
iter(level*2, max_level, start_x - Unit / level / 2.0, start_y - Unit / level / 2.0 * Sqrt3);
triangle(start_x - Unit / level / 2.0 + Unit / level * 2.0, start_y - Unit / level / 2.0 * Sqrt3,
start_x + Unit / level / 2.0 + Unit / level * 2.0, start_y - Unit / level / 2.0 * Sqrt3,
start_x + Unit / level * 2.0, start_y - Unit / level * Sqrt3);
iter(level*2, max_level, start_x - Unit / level / 2.0 + Unit / level * 2.0, start_y - Unit / level / 2.0 * Sqrt3);
}

void draw()
{
triangle(0, 0, Unit, 0, 0.5*Unit, 0.5*Unit*Sqrt3);
fill(255);
iter(2, 1024, 0, 0);
save("Sierpinski.png");
}


推薦閱讀:

使用目前的高配GPU 如何實時渲染一顆黑洞?
你寫過什麼有趣的程序?
是什麼原因導致中國電影作品特效效果普遍不如歐美電影?
什麼是實時光線追蹤技術?它可能出現在當前的次世代主機上嗎?

TAG:編程 | 趣味數學 | 圖形 | 計算機圖形學 |