希尔伯特曲线及性质的形式化理解

希尔伯特曲线是一条填满整个平面的神奇曲线, 其构造方式是把前一阶的曲线复制四份, 将左下角和右下角的曲线做一个沿对角线的翻转, 然后增加三条线段把这四份连起来.这些曲线的极限就是希尔伯特曲线.

以前对这个曲线的理解停留在感觉上, 不知道极限是什么样子, 一直想从formal定义的角度去考察一下.今天在bilibili找到一个科普视频(https://www.bilibili.com/video/av4201747/, 系列视频都比较有趣, 推荐!), 搞清了其中的内涵.

n阶的希尔伯特曲线是从[0,1]区间到[0,1]×[0,1]平面区域的映射fn, 把0和1映射到区域左下角和右下角:

fn(0)=(0,0),fn(0)=(1,0)

并且, 通过适当的调整,让每个1/4的小区间映射到4个区域内.

填充整个区域的希尔伯特曲线是这样的函数f, 使得函数列fn逐点收敛到它. 即:

f(x):=limnfn(x)

下面将说明这样的定义符合直观理解——填充区间, 曲线.

1. Hilbert曲线的性质

良定义

首先要说明这个定义是well-defined, 即对于所有的x, fn(x)确实收敛. 我认为这个可以从区间套来说明. 不管x取定义域中的什么值, 都可以不断将区间四等分, 用长度为1/4,1/16,1/64的区间套来套住, 由于不同阶Hilbert曲线的定义, 对应的函数值也落在相应的区域套内. 这样形成一系列闭区域的套, 总有一个确定的极限值.

这里有个问题就是,当x是两个四等分区间的交点时应该取左边的区间继续等分,还是取右边的区间继续等分. 这里应该能够证明取哪个得到的极限都是一样的, 这也是曲线连续性的要求.

填充整个区间

是的, Hilbert函数的取值遍布整个单位平面区域. 不信的话在[0,1]×[0,1]里面随便选一个点(x,y), 将平面不断四等分为上下左右四个闭区域, 用同样的方法, 能对应到定义域里的闭区间, 最后套出一个自变量x0来, 使得f(x0)=(x,y).

这里要是选择的点落在边界上应该选哪个区域继续四等分呢? 这时选不同的点就不一样了. 比如(1/2,0)点,其实会有左右两个x,都能逼近这个点. 这恰恰说明, Hilbert曲线, 是满射(映上的), 不是单射(1-1的), 所以也不是双射.

仍然是曲线

曲线要求是[0,1]R2上的连续映射. 这里的连续性还比较好说. 对于值域中的点(x,y), 选择一个任意小的ϵ邻域, 都可以在里面找到更小的1/4k×1/4k大的(对齐的)闭区域, 对应到定义域是一个闭区间, 然后找到更小的δ开区间, 这里的所有点都会映射到ϵ领域中.

因为Hilbert曲线不是单射, 故不存在逆映射. 不能说Hilbert曲线让直线段和平面区域拓扑同胚了.

2. 应用

有了填满单位区域上的曲线, 将它螺旋填充就能找到填满整个平面的曲线了.

这里找到了一个满射, 说明集合R的势至少和R2一样大. 其实这两个是等势的. 不过Hilbert不是一个双射. 确实存在这两个集合的双射, 好像也有人也证明了这两者的双射也不会连续.

这里的Hilbert曲线弯曲太多, 有了无穷大的长度, 甚至都占据了面积. 这有点分形的味道. 在上面的视频里也有更多说明. 感兴趣的可以去看看.

视频里面还说到一种神奇的应用: 通过耳朵来产生视觉, 脑洞很大很有趣.

3. 附录

把Hlibert曲线着色以后是这样的, 从紫色到蓝色再到绿色和黄色, 说明了自变量x不断增大:

本文中两图的生成脚本如下:

from matplotlib import pyplot as plt
import numpy as np
import math

# generate psedo Hilbert curve - the function from 1d to 2d
order = 11
phc = [
    [[0,0]]  # order 0
]
for o in range(order):
    new_phc = []
    new_phc += [[y, x] for x, y in phc[o]]  # left bottom
    new_phc += [[x, y + 2**o] for x, y in phc[o]]  # left top
    new_phc += [[x + 2**o, y + 2**o] for x, y in phc[o]]  # right top
    new_phc += [[2**o - 1 - y + 2**o, 2**o - 1 - x] for x, y in phc[o]]  # right bottom
    phc.append(new_phc)

# plot these curves
for o in range(order):
    fig = plt.figure(o, figsize=(6,6))
    plt.axis('off')
    plt.plot(
        list(map(lambda p:p[0], phc[o+1])),
        list(map(lambda p:p[1], phc[o+1]))
    )
    fig.savefig('order_{}.png'.format(o+1))
    if o+1 != order:
        plt.close(fig)
plt.show()

# colorize the pixels in order, for visualization
size = 2**order
imax = size*size
image = [[0]*size for i in range(size)]
i = 0
for x,y in phc[order]:
    image[size-1-y][x] = i
    i += 1

plt.imshow(image)
plt.show()
plt.imsave('hilbert.png', image)

posted @   zzdyyy  阅读(7232)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示