两个问题:
1.如何输出希尔伯特曲线的指定经过的第p个点坐标?
2.如何绘制出整个n阶的希尔伯特曲线(即可视化输出)?
首先,我们来说明下什么是希尔伯特曲线。
什么是希尔伯特曲线,我借用这张截图一用即可说明:
我们规定左下角方格的坐标为(0,0),右下角方格的坐标为(2的n次方-1,0)。
注意,以下n均指n阶希尔伯特曲线,p均指第p个点。
那么我们来解决第一个问题:如何输出希尔伯特曲线的指定经过的第p个点坐标?
首先我们观察这些曲线的特点,可以看见,如果把整体分成四大均匀块,那么整体来讲,一个希尔伯特图的坐标变化是++y,++x,- -y
然后我们以二阶曲线为例子(因为一阶曲线的局部是个点,所以换个二阶来看,但无论多少阶都不会影响最终结论)
观察局部的规律:
头部两个图依然是相对是(++y,++x,- -y)这样的。(为什么说相对?之后会有解释)
左下角是单个部分图向右旋转90°的形象,其坐标这时有了不同的变化,是(++x,++y,- -x)
右下角是单个部分图向左旋转90°的形象,其坐标这时也有了不同的变化,是(- -x, - -y,++x)
我们可以发现,左下角和右下角在相对位置上面存在xy置换现象,(如左下角,它把++y,++x,- -y变成了++x,++y,- -x)且右下角不仅存在xy置换现象,而且xy的增减是相反的。
总结以上,我们知道这条规律:相对的,对于整个图来说,它可以分为四大部分的图,每个部分图又可以看做为一个整体的图,继而递归下去,直至一阶图,而且无论几阶图的点的坐标走向都是++y,++x,- -y,但由于部分图存在旋转,所以在递归的时候xy会有置换。
这条规律将导致在递归过程中,xy的意义会时不时互换,因此,我们说xy都是相对意义上的xy
我们可以先准备这样一个递归函数,它将把一个n阶的希尔伯特曲线图当做一阶的来处理,只进行相对意义上的(++y,++x,- -y)操作。而在进行这三个自增自减的坐标变动操作之前,我们会把它的四分之一图(低一阶的希尔伯特曲线),从左下到左上,从左上到右上,从右上到右下的依次递归处理,直至我们遇到一阶的希尔伯特曲线。
函数声明如下
void line(int n, int *x, int *y, int face);
n代表当前是几阶希尔伯特曲线
x是相对意义的x
y是相对意义的y
face则是代表在这个相对的希尔伯特图下,x和y的值到底是该增还是该减
然后我们搭建出如下的一个代码框架
#include <iostream>
using namespace std;
int x = 0, y = 0;
long long p;
void line(int n, int *x, int *y, int face) {
...
}
int main() {
int n;
cin >> n >> p;
line(n, &x, &y, 1);
return 0;
}
p和x,y作为全局变量是有好处的,由于他们不需要回溯他们的值,所以放在外面当全局变量,可以不用每次执行递归函数都创建出他们三个临时变量。
然后他们的作用是显而易见的:
p代表当前离目标经过点还剩几步
x,y代表当前点坐标
n代表在当前递归函数处理的希尔伯特图是几阶的
该函数内部我们这么写就能处理掉当前阶的希尔伯特曲线的坐标移动问题:
void line(int n, int *x, int *y, int face) {
*y += face;//相对地,y应做自增处理
*x += face;//相对地,x应做自增处理
*y -= face;//相对地,y应做自减处理
}
然后我们知道这只是抽象地将n阶的希尔伯特曲线看作是一个拥有四大部分的一阶希尔伯特曲线,
所以我们需要对每个四分之一的n-1阶希尔伯特曲线做具体的递归处理,即:
void line(int n, int *x, int *y, int face) {
if (n < 1)return;
line(n - 1, y, x, face);
//该步是左下角的n-1阶希尔伯特曲线图,因为它相对是向右旋转90°的,所以xy需要置换他们的意义
//face是相对来说是正的,因为具体来讲,某个旋转后的希尔伯特图的坐标值增加在数值上却是减少
*y += face;
line(n - 1, x, y, face);
//该步是左上角的n-1阶希尔伯特曲线图,xy的相对意义不变,与当前n阶图意义一致
//face是相对来说是正的
*x += face;
line(n - 1, x, y, face);
//该步是右上角的n-1阶希尔伯特曲线图,xy的相对意义不变,与当前n阶图意义一致
//face是相对来说是正的
*y -= face;
line(n - 1, y, x, -face);
//该步是右下角的n-1阶希尔伯特曲线图,因为它相对是向左旋转90°的,所以xy同样需要置换他们的意义
//face是相对来说是负的,这将与左下角的情况区分
}
因为我们这时候不用遍历一个n阶希尔伯特曲线的所有点,所以我们只需要在每个坐标值即将变动之前的位置增加一些语句使其能够判断某个时刻就应该及时终止了。
这里我们用p来计数当前还剩多少个该经过的点,并用域作用符来输出终止后的xy坐标点(如果不用域作用符,将输出相对意义下的xy值,他们可能是刚好意义与本质相反的):
void line(int n, int *x, int *y, int face) {
if (n < 1)return;
line(n - 1, y, x, face);
if (--p == 0) {
cout << ::x << " " << ::y;
exit(0);
}
*y += face;
line(n - 1, x, y, face);
if (--p == 0) {
cout << ::x << " " << ::y;
exit(0);
}
*x += face;
line(n - 1, x, y, face);
if (--p == 0) {
cout << ::x << " " << ::y;
exit(0);
}
*y -= face;
line(n - 1, y, x, -face);
}
完整代码如下:
#include <iostream>
using namespace std;
int x = 0, y = 0;
long long p;
void line(int n, int *x, int *y, int face) {
if (n < 1)return;
line(n - 1, y, x, face);
if (--p == 0) {
cout << ::x << " " << ::y;
exit(0);
}
*y += face;
line(n - 1, x, y, face);
if (--p == 0) {
cout << ::x << " " << ::y;
exit(0);
}
*x += face;
line(n - 1, x, y, face);
if (--p == 0) {
cout << ::x << " " << ::y;
exit(0);
}
*y -= face;
line(n - 1, y, x, -face);
}
int main() {
int n;
cin >> n >> p;
line(n, &x, &y, 1);
return 0;
}
输出结果:
一阶:
二阶:
三阶的也没有问题
以上,我们就解决了第一个问题。
当然,如果你认为这些点可能只是恰好对了而已,那么我们对他做下可视化处理,让我们看看,完整的希尔伯特图,是不是能够被完整且完美的遍历然后显示出来吧。
那么由于上面的代码,去掉关于p的部分后就可以做到遍历,于是,第二个问题的解决代码就直接放出来了。
我这里采用easyx库来做可视化处理,代码里追加的图形化代码如下:
#include <iostream>
#include <graphics.h>
#include <math.h>
using namespace std;
int x = 0, y = 0;
long long p;
int width;
#define LENGTH (50)
void line(int n, int *x, int *y, int face) {
if (n < 1)
return;
line(n - 1, y, x, face);
*y += face;
if (y == &::y)
line((LENGTH / 2) + ::x * LENGTH, width - (::y - face) * LENGTH - (LENGTH / 2), (LENGTH / 2) + ::x * LENGTH, width - ::y * LENGTH - (LENGTH / 2));
else
line((LENGTH / 2) + (::x - face) * LENGTH, width - ::y * LENGTH - (LENGTH / 2), (LENGTH / 2) + ::x * LENGTH, width - ::y * LENGTH - (LENGTH / 2));
line(n - 1, x, y, face);
*x += face;
if (y == &::y)
line((LENGTH / 2) + (::x - face) * LENGTH, width - ::y * LENGTH - (LENGTH / 2), (LENGTH / 2) + ::x * LENGTH, width - ::y * LENGTH - (LENGTH / 2));
else
line((LENGTH / 2) + ::x * LENGTH, width - (::y - face) * LENGTH - (LENGTH / 2), (LENGTH / 2) + ::x * LENGTH, width - ::y * LENGTH - (LENGTH / 2));
line(n - 1, x, y, face);
*y -= face;
if (y == &::y)
line((LENGTH / 2) + ::x * LENGTH, width - (::y + face) * LENGTH - (LENGTH / 2), (LENGTH / 2) + ::x * LENGTH, width - ::y * LENGTH - (LENGTH / 2));
else
line((LENGTH / 2) + (::x + face) * LENGTH, width - ::y * LENGTH - (LENGTH / 2), (LENGTH / 2) + ::x * LENGTH, width - ::y * LENGTH - (LENGTH / 2));
line(n - 1, y, x, -face);
}
int main() {
int n;
cin >> n;
width = pow(2, n) * LENGTH;
initgraph(width, width);
line(n, &x, &y, 1);
Sleep(3000);
closegraph();
return 0;
}
最后,我们来看看遍历后的可视化结果:
(一阶)
(二阶)
(三阶)
(四阶)