在Processing中想要创作复杂的图形元素,除了可以使用循环结构外,还可以使用递归 (recursion) 。递归是一种在函数执行时调用自身的一种特殊设计方法,递归可以解决很多循环结构无法解决的问题,同时在代码实现上也极为简洁。
为了可以更直观的了解什么是递归,这里先给出一个例子:
// Recursive Circle // YunFei void setup() { size(420, 420); drawCircle(400); } void draw() { noLoop(); } void drawCircle(float radius) { if (radius < 10) { return; } ellipse(width/2, height/2, radius, radius); drawCircle(radius - 20); }
在上例中,drawCircle() 函数在返回前再次调用了drawCircle(),同时半径参数减小了20。所以drawCircle(400)会调用drawCircle(380),drawCircle(380)绘制完毕后又会调用drawCircle(360),… …,直到drawCircle(20)调用drawCircle(0)。此时,因为radius小于给定的容限(radius < 10),drawCircle(0)直接执行了return语句返回了该函数,从而不会再继续调用drawCircle(-20)。随着drawCircle(0)的返回,其余函数也逐级返回,直到所有嵌套的函数完全结束。
递归虽然相比循环在代码上更为简洁,但是设计难度却大于循环结构。设计递归时有两个注意点:
1)是程序的规模要不断减小 (此处每次radius – 20),否则程序会陷入死循环无法退出;
2)有一个合适的中止条件 (此处为radius < 10),一般来说小于某个容限值比等于某个极限值更为可靠。
下面以一个更复杂的例子,树型分形,来展现递归的美妙(代码修改于Daniel Shiffman, The Nature of Code):
// Recursive Tree // YunFei void setup() { size(500, 500); background(255); translate(width/2, height); stroke(0); drawBranch(150); } void draw() { noLoop(); } void drawBranch(float len) { float theta = PI/6; strokeWeight(2); line(0, 0, 0, -len); translate(0, -len); len *= 0.66; if (len > 10) { pushMatrix(); rotate(theta); drawBranch(len); popMatrix(); pushMatrix(); rotate(-theta); drawBranch(len); popMatrix(); } }
以上分形图形随着枝的逐层生长,分支的数量呈指数倍、而非线性增长,循环结构无法解决该问题。可见,灵活使用递归可以创造更为复杂的图形元素。实际上,递归更多情况下被用于求解数学问题,如最为经典的牛顿迭代法求解方程,就可以使用递归函数来计算。