希爾伯特曲線是一條填滿整個平面的神奇曲線, 其構造方式是把前一階的曲線復制四份, 將左下角和右下角的曲線做一個沿對角線的翻轉, 然后增加三條線段把這四份連起來.這些曲線的極限就是希爾伯特曲線.
以前對這個曲線的理解停留在感覺上, 不知道極限是什么樣子, 一直想從formal定義的角度去考察一下.今天在bilibili找到一個科普視頻(https://www.bilibili.com/video/av4201747/, 系列視頻都比較有趣, 推薦!), 搞清了其中的內涵.
n階的希爾伯特曲線是從\([0, 1]\)區間到\([0, 1]\times[0, 1]\)平面區域的映射\(f_n\), 把0和1映射到區域左下角和右下角:
並且, 通過適當的調整,讓每個1/4的小區間映射到4個區域內.
填充整個區域的希爾伯特曲線是這樣的函數\(f\), 使得函數列\(f_n\)逐點收斂到它. 即:
下面將說明這樣的定義符合直觀理解——填充區間, 曲線.
1. Hilbert曲線的性質
良定義
首先要說明這個定義是well-defined, 即對於所有的\(x\), \({f_n(x)}\)確實收斂. 我認為這個可以從區間套來說明. 不管\(x\)取定義域中的什么值, 都可以不斷將區間四等分, 用長度為1/4,1/16,1/64的區間套來套住, 由於不同階Hilbert曲線的定義, 對應的函數值也落在相應的區域套內. 這樣形成一系列閉區域的套, 總有一個確定的極限值.
這里有個問題就是,當\(x\)是兩個四等分區間的交點時應該取左邊的區間繼續等分,還是取右邊的區間繼續等分. 這里應該能夠證明取哪個得到的極限都是一樣的, 這也是曲線連續性的要求.
填充整個區間
是的, Hilbert函數的取值遍布整個單位平面區域. 不信的話在\([0, 1]\times[0, 1]\)里面隨便選一個點\((x, y)\), 將平面不斷四等分為上下左右四個閉區域, 用同樣的方法, 能對應到定義域里的閉區間, 最后套出一個自變量\(x_0\)來, 使得\(f(x_0) = (x,y)\).
這里要是選擇的點落在邊界上應該選哪個區域繼續四等分呢? 這時選不同的點就不一樣了. 比如(1/2,0)點,其實會有左右兩個\(x\),都能逼近這個點. 這恰恰說明, Hilbert曲線, 是滿射(映上的), 不是單射(1-1的), 所以也不是雙射.
仍然是曲線
曲線要求是\([0,1]\)到\(\mathbb{R}^2\)上的連續映射. 這里的連續性還比較好說. 對於值域中的點\((x,y)\), 選擇一個任意小的\(\epsilon\)鄰域, 都可以在里面找到更小的\(1/4^k \times 1/4^k\)大的(對齊的)閉區域, 對應到定義域是一個閉區間, 然后找到更小的\(\delta\)開區間, 這里的所有點都會映射到\(\epsilon\)領域中.
因為Hilbert曲線不是單射, 故不存在逆映射. 不能說Hilbert曲線讓直線段和平面區域拓撲同胚了.
2. 應用
有了填滿單位區域上的曲線, 將它螺旋填充就能找到填滿整個平面的曲線了.
這里找到了一個滿射, 說明集合\(\mathbb{R}\)的勢至少和\(\mathbb{R}^2\)一樣大. 其實這兩個是等勢的. 不過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)