这个世界的维度不一定是整数——谈谈分数维是怎么回事

我们日常接触的维度都是整数:线段是 1 维的,正方形是 2 维的,立方体是 3 维的。这个东西很直观——你从小到大在黑板上画的坐标系,横轴是一条线,横轴加纵轴是一个面,再加一根竖轴就是空间。

但这个世界有些东西,它的维度不是整数。

整数维度和分数维度的对比

英国的海岸线有多长?你拿一把很长的尺子去量,得到一个数字。换一把更短的尺子,能量到更多弯弯绕绕,量出来的长度变长了。再换更短的尺子,它变得更长。如果尺子无限短,海岸线的长度似乎会变成无限大——但它又不是 2 维的面,它是一根线,只不过缠绕得过于厉害。

Mandlebrot 在 1967 年那篇著名的 Science 论文里问了这个问题。他的答案是:海岸线的维度在 1 和 2 之间,大概 1.2 到 1.3 左右。这不是一个整数。

维度到底怎么算

先回想一下整数维度是怎么来的。拿一条线段,把它均分成 2 段——你得到的是 2 段,每段长度是原来的 1/2。如果你把它均分成 3 段,你得到 3 段,每段长度 1/3。

关键在哪呢?放大的倍数和段数的关系:

  • 线段:放大 2 倍 → 长度变成 2 倍。2 = 2¹。维度是 1。
  • 正方形:放大 2 倍 → 面积变成 4 倍。4 = 2²。维度是 2。
  • 立方体:放大 2 倍 → 体积变成 8 倍。8 = 2³。维度是 3。

设放大倍数为 r 的倒数(就是缩小的比例),设 N 为缩小后小图形的个数,则维度 D 满足:

\[ N = \left(\frac{1}{r}\right)^D \]

两边取对数:

\[ D = \frac{\log N}{\log (1/r)} \]

三个形状的缩放维度计算

这个公式妙在什么地方呢?它不要求 N 是整数。只要你能数清楚"一个图形缩小到原来的几分之一后,包含了几个和自己相似的更小图形",你就能算出它的维度——哪怕算出来的不是整数。

科赫雪花:周长无限但面积有限

科赫雪花(Koch Snowflake)可能是最著名的分形了。构造方法非常简单:

  1. 从一个等边三角形开始
  2. 每条边三等分,把中间那段换成一个小的等边三角形的两条边
  3. 重复

用 Python 写出来就是:

import numpy as np
import matplotlib.pyplot as plt

def koch_snowflake(depth=4):
    """生成科赫雪花的顶点坐标"""
    # 初始等边三角形
    pts = np.array([[0, 0], [1, 0],
                    [0.5, np.sin(np.pi/3)], [0, 0]])

    for _ in range(depth):
        new_pts = []
        for i in range(len(pts) - 1):
            x1, y1 = pts[i]
            x2, y2 = pts[i+1]
            dx, dy = (x2 - x1) / 3, (y2 - y1) / 3

            # 第一段起点
            new_pts.append((x1, y1))
            # 第二段起点 (三等分点)
            new_pts.append((x1 + dx, y1 + dy))
            # 中间凸起的顶点 (旋转60度)
            angle = np.pi / 3
            mx, my = x1 + 1.5*dx, y1 + 1.5*dy
            new_pts.append((mx - dx*np.cos(angle) + dy*np.sin(angle),
                            my - dx*np.sin(angle) - dy*np.cos(angle)))
            # 第三段起点 (二等分点)
            new_pts.append((x1 + 2*dx, y1 + 2*dy))
        new_pts.append(pts[-1])
        pts = np.array(new_pts)

    return pts

pts = koch_snowflake(4)
plt.figure(figsize=(8, 8))
plt.fill(pts[:, 0], pts[:, 1], facecolor='#f39c12',
         alpha=0.4, edgecolor='#f39c12', linewidth=0.5)
plt.axis('equal'); plt.axis('off')
plt.show()

科赫雪花的四次迭代过程

每一次迭代都发生了什么?一条线段被替换成了 4 条更短的线段,每条的长度是原来的 1/3。

所以 N = 4,r = 1/3(或者说,放大倍数是 3)。

\[ D = \frac{\log 4}{\log 3} \approx \frac{0.602}{0.477} \approx 1.26 \]

科赫雪花的维度大约是 1.26。它是一个在 1 和 2 之间的几何对象——比一条线更"拥挤",但还远不到一个面。它的一个非常反直觉的性质是:面积是有限的,但周长是无限的。每一次迭代,周长都变成原来的 4/3,无限迭代下去周长就趋于无穷。

谢尔宾斯基三角形:像是被虫蛀过的三角形

另一个经典分形是谢尔宾斯基三角形(Sierpinski Triangle)。构造方法也很简单:

  1. 一个黑色实心三角形
  2. 挖掉中间那个倒着的、边长是原来一半的小三角形
  3. 对剩下的三个小三角形重复同样的操作
def sierpinski(depth=4):
    """生成谢尔宾斯基三角形"""
    pts = np.array([[0., 0.], [1., 0.],
                    [0.5, np.sin(np.pi/3)]])
    triangles = [pts]

    for _ in range(depth):
        new_tris = []
        for t in triangles:
            a, b, c = t[0], t[1], t[2]
            # 三条边的中点
            mab = (a + b) / 2
            mbc = (b + c) / 2
            mca = (c + a) / 2
            # 三个小三角形(中间那个被挖掉了)
            new_tris.append(np.array([a, mab, mca]))
            new_tris.append(np.array([mab, b, mbc]))
            new_tris.append(np.array([mca, mbc, c]))
        triangles = new_tris
    return triangles

谢尔宾斯基三角形的四次迭代

每次迭代,三角形被分成 4 个等大的小三角形(中间那个被挖掉所以只剩下 3 个)。每个小三角形的边长是原来的一半。所以 N = 3,r = 1/2。

\[ D = \frac{\log 3}{\log 2} \approx \frac{0.477}{0.301} \approx 1.58 \]

谢尔宾斯基三角形的维度大概是 1.58。比科赫曲线更接近 2,但仍然不是一个完整的平面——因为它被无限多个窟窿蛀空了,总面积趋近于 0。

计算任何自相似分形的维度的通用函数:

import math

def fractal_dimension(N, r):
    """
    计算自相似分形的维度
    N: 缩小后的小图形个数
    r: 缩小的比例 (新边长 / 原边长)
    """
    return math.log(N) / math.log(1/r)

print(f"科赫雪花: {fractal_dimension(4, 1/3):.2f}")
print(f"谢尔宾斯基三角形: {fractal_dimension(3, 1/2):.2f}")
print(f"门格海绵: {fractal_dimension(20, 1/3):.2f}")
print(f"线段: {fractal_dimension(2, 1/2):.2f}")
print(f"正方形: {fractal_dimension(4, 1/2):.2f}")

输出:

科赫雪花: 1.26
谢尔宾斯基三角形: 1.58
门格海绵: 2.73
线段: 1.00
正方形: 2.00

注意最后一行的线段和正方形——N 和 r 取适当的值,公式算出整数维度。这个公式是兼容的,整数维度只是它的特例。

Mandelbrot 集:无穷的精致

如果说科赫雪花和谢尔宾斯基三角形是数学家人工构造的,那 Mandelbrot 集就是大自然塞到数学里的。它的定义简单到只有一行:

\[ z_{n+1} = z_n^2 + c \]

取一个复数 c,从 z₀ = 0 开始不断迭代这个公式。如果 z 最终发散到无穷大,c 就不属于 Mandelbrot 集;如果永远局限在有限范围内,c 就属于它。你只是把每一个 c 在复平面上对应的点按照迭代发散的快慢染上不同的颜色,就得到了下面这个东西。

Mandelbrot 全集与局部放大

用 Python 画出来:

import numpy as np
import matplotlib.pyplot as plt

def mandelbrot(width, height, max_iter, xmin, xmax, ymin, ymax):
    x = np.linspace(xmin, xmax, width)
    y = np.linspace(ymin, ymax, height)
    C = x[:, None] + 1j * y[None, :]
    Z = np.zeros_like(C)
    img = np.zeros((height, width))

    for i in range(max_iter):
        mask = np.abs(Z) < 4
        Z[mask] = Z[mask]**2 + C[mask]
        img[~mask] = i
        img[mask] = i

    return img.T

img = mandelbrot(800, 600, 200, -2.5, 1.0, -1.3, 1.3)
plt.figure(figsize=(10,7),facecolor='#0f1720')
plt.imshow(img, cmap='hot', extent=[-2.5,1.0,-1.3,1.3])
plt.xticks([]); plt.yticks([])
plt.show()

这个集合有一个极为特殊的性质:它的边界是处处粗糙的。不管你把放大倍数调到多大,边界上总有新的结构冒出来。你放大到原子尺度,它依然有凸起、凹陷、螺旋——永远看不完。正是因为这个边界复杂到了极致,它的 Hausdorff 维数被证明等于 2。也就是说,一条"线"——如果它可以被叫做线的话——复杂到了填满一个平面的程度。

分数维的存在告诉我们什么

维度不是一个必须整数的东西——它更像是在描述"这个形状在一个空间里填充了多少"。科赫雪花比线更挤所以维度比 1 大,但不如面那么密集所以维度比 2 小。谢尔宾斯基三角形比科赫雪花更靠近 2,因为它覆盖的面积比例更大。

现实中很多东西的维度可能不是整数。海岸线、血管网络、肺的支气管树、河网水系——这些自然形状都或多或少有自相似性,它们的有效维度是一个比整数更有信息量的描述方式。

这也许也可以当作一种思维方式来琢磨。日常做判断的时候,很多事并不是非黑即白、非零即一的简单二分。在 1 和 0 之间、在 1 和 2 之间,可能存在着连续的、丰富的光谱式的过渡。不是维度可以随便定义,而是很多看起来理所当然的"整数"划分,换个角度去看就有更多东西在中间。