目录
题目:**18.37 (希尔伯特曲线)
代码示例
代码逻辑解释
1. 初始化与布局
2. 绘制逻辑
3. 绘制过程
输出结果
题目:**18.37 (希尔伯特曲线)
希尔伯特曲线,由德国数学家希尔伯特于1891年第一个给出描述,是一种空间填充曲线,以 2 x 2, 4 x 4, 8 x 8, 16 x 16, 或者任何其他 2 的幂的大小来访问一个方格网的每个点。编写程序,以给定的阶数显示希尔伯特曲线,如图 18-19 所示。
-
代码示例
编程练习题18_37HilbertCurve.java
package chapter_18;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class 编程练习题18_37HilbertCurve extends Application {
private static final int WIDTH = 700;
private static final int HEIGHT = 700;
private static final int MARGIN = 10;
private double segX, segY;
private int[] EXP = new int[10];
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// Initialize EXP array
EXP[0] = 1;
for (int i = 1; i < 10; i++) {
EXP[i] = EXP[i - 1] * 2;
}
// Create UI components
Label lblOrder = new Label("Enter the order: ");
TextField txtOrder = new TextField();
Canvas canvas = new Canvas(WIDTH, HEIGHT);
GraphicsContext gc = canvas.getGraphicsContext2D();
// Layout
HBox hBox = new HBox(lblOrder, txtOrder);
hBox.setAlignment(Pos.CENTER);
VBox vBox = new VBox(canvas,hBox);
// Event handler for the button
txtOrder.setOnKeyPressed(event -> {
if(event.getCode() == KeyCode.ENTER&&event.getText() != "") {
try {
int order = Integer.parseInt(txtOrder.getText());
if (order > 0 && order < 10) {
gc.clearRect(0, 0, WIDTH, HEIGHT);
segX = (WIDTH - 2 * MARGIN) / (EXP[order] - 1.0);
segY = (HEIGHT - 2 * MARGIN) / (EXP[order] - 1.0);
showOrder(0, 0, 1, order, gc);
}
} catch (NumberFormatException e) {
//System.out.println("Please enter a valid integer.");
System.out.println(e);
}
}
});
// Scene and stage setup
Scene scene = new Scene(vBox);
primaryStage.setTitle(getClass().getName());
primaryStage.setScene(scene);
primaryStage.show();
}
private void showOrder(int startX, int startY, int shape, int order, GraphicsContext gc) {
if (order == 1) {
switch (shape) {
case 1:
drawLine(gc, startX, startY, startX, startY + 1);
drawLine(gc, startX, startY + 1, startX + 1, startY + 1);
drawLine(gc, startX + 1, startY + 1, startX + 1, startY);
break;
case 2:
drawLine(gc, startX, startY, startX + 1, startY);
drawLine(gc, startX + 1, startY, startX + 1, startY + 1);
drawLine(gc, startX + 1, startY + 1, startX, startY + 1);
break;
case 3:
drawLine(gc, startX, startY + 1, startX, startY);
drawLine(gc, startX, startY, startX + 1, startY);
drawLine(gc, startX + 1, startY, startX + 1, startY + 1);
break;
case 4:
drawLine(gc, startX + 1, startY, startX, startY);
drawLine(gc, startX, startY, startX, startY + 1);
drawLine(gc, startX, startY + 1, startX + 1, startY + 1);
break;
}
} else {
switch (shape) {
case 1:
showOrder(startX, startY, 2, order - 1, gc);
drawLine(gc, startX, startY + EXP[order - 1] - 1, startX, startY + EXP[order - 1]);
showOrder(startX, startY + EXP[order - 1], 1, order - 1, gc);
drawLine(gc, startX + EXP[order - 1] - 1, startY + EXP[order - 1], startX + EXP[order - 1], startY + EXP[order - 1]);
showOrder(startX + EXP[order - 1], startY + EXP[order - 1], 1, order - 1, gc);
drawLine(gc, startX + EXP[order] - 1, startY + EXP[order - 1], startX + EXP[order] - 1, startY + EXP[order - 1] - 1);
showOrder(startX + EXP[order - 1], startY, 4, order - 1, gc);
break;
case 2:
showOrder(startX, startY, 1, order - 1, gc);
drawLine(gc, startX + EXP[order - 1] - 1, startY, startX + EXP[order - 1], startY);
showOrder(startX + EXP[order - 1], startY, 2, order - 1, gc);
drawLine(gc, startX + EXP[order - 1], startY + EXP[order - 1] - 1, startX + EXP[order - 1], startY + EXP[order - 1]);
showOrder(startX + EXP[order - 1], startY + EXP[order - 1], 2, order - 1, gc);
drawLine(gc, startX + EXP[order - 1] - 1, startY + EXP[order] - 1, startX + EXP[order - 1], startY + EXP[order] - 1);
showOrder(startX, startY + EXP[order - 1], 3, order - 1, gc);
break;
case 3:
showOrder(startX, startY + EXP[order - 1], 2, order - 1, gc);
drawLine(gc, startX, startY + EXP[order - 1], startX, startY + EXP[order - 1] - 1);
showOrder(startX, startY, 3, order - 1, gc);
drawLine(gc, startX + EXP[order - 1] - 1, startY + EXP[order - 1] - 1, startX + EXP[order - 1], startY + EXP[order - 1] - 1);
showOrder(startX + EXP[order - 1], startY, 3, order - 1, gc);
drawLine(gc, startX + EXP[order] - 1, startY + EXP[order - 1] - 1, startX + EXP[order] - 1, startY + EXP[order - 1]);
showOrder(startX + EXP[order - 1], startY + EXP[order - 1], 4, order - 1, gc);
break;
case 4:
showOrder(startX + EXP[order - 1], startY, 1, order - 1, gc);
drawLine(gc, startX + EXP[order - 1], startY, startX + EXP[order - 1] - 1, startY);
showOrder(startX, startY, 4, order - 1, gc);
drawLine(gc, startX + EXP[order - 1] - 1, startY + EXP[order - 1] - 1, startX + EXP[order - 1] - 1, startY + EXP[order - 1]);
showOrder(startX, startY + EXP[order - 1], 4, order - 1, gc);
drawLine(gc, startX + EXP[order - 1] - 1, startY + EXP[order] - 1, startX + EXP[order - 1], startY + EXP[order] - 1);
showOrder(startX + EXP[order - 1], startY + EXP[order - 1], 3, order - 1, gc);
break;
}
}
}
private void drawLine(GraphicsContext gc, int x1, int y1, int x2, int y2) {
gc.strokeLine(MARGIN + x1 * segX, MARGIN + y1 * segY, MARGIN + x2 * segX, MARGIN + y2 * segY);
}
}
-
代码逻辑解释
1. 初始化与布局
- 变量初始化:定义了一些常量(如窗口的宽度和高度、边距等)和数组
EXP
,用于存储2的幂次方值,方便后续计算。 - UI组件创建:创建了一个文本输入框(
TextField
)用于输入阶数,一个标签(Label
)用于提示用户输入,一个画布(Canvas
)用于绘制曲线,以及相关的布局组件(HBox
和VBox
)。 - 事件处理:为文本输入框设置了一个键盘事件监听器,当用户按下回车键并且输入的内容是有效的整数时,会根据输入的阶数来绘制希尔伯特曲线。
2. 绘制逻辑
-
showOrder
方法:这是绘制希尔伯特曲线的核心方法,它根据阶数(order
)和当前绘制的形状(shape
,用1到4表示四种不同的递归方向)来递归地绘制曲线。当阶数为1时,直接绘制基础的四段直线;当阶数大于1时,根据递归规则和当前形状来决定下一步的绘制方向和位置。 -
递归规则:对于每个阶数大于1的情况,
showOrder
方法会根据当前的shape
值选择一种递归模式,每种模式都会先递归绘制子曲线,然后绘制连接子曲线的线段,最后继续递归绘制其他子曲线。这种递归模式确保了曲线能够填充整个二维空间,并且保持了曲线的连续性。 -
drawLine
方法:这是一个辅助方法,用于在画布上绘制线段。它接收线段的起点和终点坐标,然后考虑到边距和当前阶数的线段长度(segX
和segY
),计算出实际绘制时的坐标,并使用GraphicsContext
对象的strokeLine
方法来绘制线段。
3. 绘制过程
- 当用户输入阶数并按下回车键时,程序会首先验证输入的有效性,然后根据输入的阶数初始化线段长度(
segX
和segY
),这两个值用于计算实际绘制时线段的像素长度。 - 接着,程序会调用
showOrder
方法来开始绘制曲线。在绘制过程中,会根据递归规则和当前形状逐步绘制出整个希尔伯特曲线。 - 每次递归调用都会将画布上的已绘制内容缩小到当前子曲线的范围,并通过调整坐标来确保子曲线能够正确地绘制在父曲线的指定位置。
-
输出结果