Skip to content

Commit 4ebf7ee

Browse files
committed
feat: 实现二叉树居中显示、NULL节点绘制和代码注释
1. 二叉树居中显示: - 修改 calculateTreeLayout 函数,使树在画布中央水平和垂直居中 - 在 TreeVisualization 组件内部进行布局计算 2. 空节点(NULL节点)显示: - 修改 D3TreeNode 类型,添加 isNull 属性 - 修改 convertToD3Tree 函数,为非叶子节点的空子节点创建 NULL 节点 - 用虚线边框和虚线连接线绘制 NULL 节点 3. 代码注释: - 为所有组件添加详细的中文注释 - 包括组件功能说明、属性接口说明、函数说明等
1 parent 2355d14 commit 4ebf7ee

File tree

10 files changed

+477
-87
lines changed

10 files changed

+477
-87
lines changed

src/App.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
/**
2+
* 应用主组件
3+
*
4+
* LeetCode 543. 二叉树的直径 - 算法可视化
5+
*
6+
* 这是一个交互式的算法可视化工具,帮助用户理解二叉树直径算法的执行过程。
7+
*
8+
* 主要功能:
9+
* - 输入二叉树数据(支持手动输入、预设样例、随机生成)
10+
* - 可视化展示二叉树结构
11+
* - 逐步演示算法执行过程
12+
* - 同步显示代码执行位置和变量状态
13+
*/
14+
115
import { useState, useEffect, useCallback, useMemo } from 'react';
216
import { Header } from './components/Header/Header';
317
import { InputPanel } from './components/InputPanel/InputPanel';
@@ -6,28 +20,44 @@ import { CodePanel } from './components/CodePanel/CodePanel';
620
import { ControlPanel } from './components/ControlPanel/ControlPanel';
721
import { WeChatFloat } from './components/WeChatFloat/WeChatFloat';
822

9-
import { buildTreeFromArray, convertToD3Tree, calculateTreeLayout } from './utils/treeUtils';
23+
import { buildTreeFromArray, convertToD3Tree } from './utils/treeUtils';
1024
import { generateAlgorithmSteps } from './algorithm/diameterAlgorithm';
1125
import './App.css';
1226

27+
// ========== 配置常量 ==========
28+
/** 默认的树数据,对应 LeetCode 示例1 */
1329
const DEFAULT_DATA: (number | null)[] = [1, 2, 3, 4, 5];
30+
/** 自动播放时每步之间的间隔时间(毫秒) */
1431
const PLAY_INTERVAL = 1500;
1532

33+
/**
34+
* App 主组件
35+
*
36+
* 状态管理:
37+
* - treeData: 当前的树数据(LeetCode 数组格式)
38+
* - currentStepIndex: 当前显示的算法步骤索引
39+
* - isPlaying: 是否正在自动播放
40+
*/
1641
function App() {
42+
// 树数据状态
1743
const [treeData, setTreeData] = useState<(number | null)[]>(DEFAULT_DATA);
44+
// 当前步骤索引
1845
const [currentStepIndex, setCurrentStepIndex] = useState(0);
46+
// 是否正在自动播放
1947
const [isPlaying, setIsPlaying] = useState(false);
2048

21-
// 构建树和生成步骤
49+
/**
50+
* 构建树和生成算法步骤
51+
*
52+
* 使用 useMemo 缓存计算结果,只在 treeData 变化时重新计算
53+
* 注意:布局计算在 TreeVisualization 组件内部进行,以确保使用正确的容器尺寸
54+
*/
2255
const { d3Tree, steps } = useMemo(() => {
56+
// 从数组构建二叉树
2357
const tree = buildTreeFromArray(treeData);
58+
// 转换为 D3 可视化格式(包含空节点)
2459
const d3Tree = convertToD3Tree(tree);
25-
26-
// 计算布局
27-
if (d3Tree) {
28-
calculateTreeLayout(d3Tree, 500, 300);
29-
}
30-
60+
// 生成算法执行步骤
3161
const steps = generateAlgorithmSteps(d3Tree);
3262
return { d3Tree, steps };
3363
}, [treeData]);

src/components/AlgorithmModal/AlgorithmModal.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
1+
/**
2+
* 算法思路弹窗组件
3+
*
4+
* 以图文并茂的方式详细解释二叉树直径算法的核心思路,
5+
* 帮助用户理解算法的原理和实现。
6+
*/
7+
18
import { useEffect, useCallback } from 'react';
29
import './AlgorithmModal.css';
310

11+
/**
12+
* AlgorithmModal 组件的属性接口
13+
*/
414
interface AlgorithmModalProps {
15+
/** 弹窗是否打开 */
516
isOpen: boolean;
17+
/** 关闭弹窗的回调函数 */
618
onClose: () => void;
719
}
820

921
/**
10-
* 算法思路弹窗组件
11-
* 详细解释二叉树直径算法的核心思路
22+
* AlgorithmModal 组件
23+
*
24+
* 功能:
25+
* - 显示算法的详细思路讲解
26+
* - 包含问题定义、核心思路、递归过程、示例分析、复杂度分析等内容
27+
* - 支持 ESC 键关闭
28+
* - 点击遮罩层关闭
29+
* - 打开时禁止背景滚动
1230
*/
1331
export function AlgorithmModal({ isOpen, onClose }: AlgorithmModalProps) {
14-
// ESC键关闭弹窗
32+
/**
33+
* 键盘事件处理:ESC 键关闭弹窗
34+
*/
1535
const handleKeyDown = useCallback((e: KeyboardEvent) => {
1636
if (e.key === 'Escape') {
1737
onClose();

src/components/CodePanel/CodePanel.tsx

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
1+
/**
2+
* 代码面板组件
3+
*
4+
* 显示算法的 Java 代码,并根据当前执行步骤:
5+
* - 高亮当前执行的代码行
6+
* - 在相关行旁边显示变量的当前值
7+
*/
8+
19
import { useMemo } from 'react';
210
import { AlgorithmStep, VariableState } from '../../types';
311
import { parseCodeLines } from '../../algorithm/diameterAlgorithm';
412
import './CodePanel.css';
513

14+
/**
15+
* CodePanel 组件的属性接口
16+
*/
617
interface CodePanelProps {
18+
/** 当前算法执行步骤,包含高亮行号和变量状态 */
719
currentStep: AlgorithmStep | null;
820
}
921

22+
/**
23+
* CodePanel 组件
24+
*
25+
* 功能:
26+
* - 显示带行号的 Java 代码
27+
* - 根据当前步骤高亮对应的代码行
28+
* - 在代码行旁边显示当前变量的值
29+
* - 提供语法高亮(关键字、类名、方法名、数字等)
30+
*/
1031
export function CodePanel({ currentStep }: CodePanelProps) {
32+
// 解析代码为行数组,只需要执行一次
1133
const codeLines = useMemo(() => parseCodeLines(), []);
34+
// 当前高亮的行号
1235
const currentLine = currentStep?.codeLineNumber || 0;
1336

14-
// 创建变量映射:行号 -> 变量列表
37+
/**
38+
* 创建变量映射:行号 -> 变量列表
39+
* 用于在对应的代码行旁边显示变量值
40+
*/
1541
const variablesByLine = useMemo(() => {
1642
const variables = currentStep?.variables || [];
1743
const map = new Map<number, VariableState[]>();
@@ -60,36 +86,54 @@ export function CodePanel({ currentStep }: CodePanelProps) {
6086
);
6187
}
6288

63-
// 简单的语法高亮
89+
/**
90+
* 简单的 Java 语法高亮函数
91+
*
92+
* 将代码中的关键字、类名、方法名、数字和注释用不同的 CSS 类包裹,
93+
* 以便在 CSS 中设置不同的颜色。
94+
*
95+
* @param code - 原始代码字符串
96+
* @returns 带有 HTML 标签的高亮代码
97+
*/
6498
function highlightSyntax(code: string): string {
65-
// 关键字
99+
// Java 关键字列表
66100
const keywords = ['class', 'private', 'public', 'int', 'if', 'return', 'null', 'new'];
101+
102+
// 首先转义 HTML 特殊字符,防止 XSS
67103
let result = escapeHtml(code);
68104

69-
// 高亮关键字
105+
// 高亮关键字(紫色)
70106
for (const kw of keywords) {
71107
const regex = new RegExp(`\\b${kw}\\b`, 'g');
72108
result = result.replace(regex, `<span class="keyword">${kw}</span>`);
73109
}
74110

75-
// 高亮类名
111+
// 高亮类名(青色)
76112
result = result.replace(/\b(TreeNode|Solution|Math)\b/g, '<span class="class-name">$1</span>');
77113

78-
// 高亮方法名
114+
// 高亮方法名(黄色)
79115
result = result.replace(/\b(diameterOfBinaryTree|depth|max)\b/g, '<span class="method-name">$1</span>');
80116

81-
// 高亮数字
117+
// 高亮数字(橙色)
82118
result = result.replace(/\b(\d+)\b/g, '<span class="number">$1</span>');
83119

84-
// 高亮注释
120+
// 高亮注释(灰色)
85121
result = result.replace(/(\/\/.*)$/gm, '<span class="comment">$1</span>');
86122

87123
return result;
88124
}
89125

126+
/**
127+
* HTML 特殊字符转义函数
128+
*
129+
* 将 HTML 特殊字符转换为实体,防止 XSS 攻击
130+
*
131+
* @param text - 原始文本
132+
* @returns 转义后的安全文本
133+
*/
90134
function escapeHtml(text: string): string {
91135
return text
92-
.replace(/&/g, '&amp;')
93-
.replace(/</g, '&lt;')
94-
.replace(/>/g, '&gt;');
136+
.replace(/&/g, '&amp;') // & 必须最先替换
137+
.replace(/</g, '&lt;') // 小于号
138+
.replace(/>/g, '&gt;'); // 大于号
95139
}

src/components/ControlPanel/ControlPanel.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,48 @@
1+
/**
2+
* 控制面板组件
3+
*
4+
* 提供算法演示的播放控制功能,包括:
5+
* - 播放/暂停
6+
* - 上一步/下一步
7+
* - 进度条拖拽
8+
* - 重置
9+
* - 键盘快捷键支持
10+
*/
11+
112
import { useEffect, useCallback } from 'react';
213
import './ControlPanel.css';
314

15+
/**
16+
* ControlPanel 组件的属性接口
17+
*/
418
interface ControlPanelProps {
19+
/** 当前步骤索引(从0开始) */
520
currentStep: number;
21+
/** 总步骤数 */
622
totalSteps: number;
23+
/** 是否正在自动播放 */
724
isPlaying: boolean;
25+
/** 上一步回调 */
826
onPrevious: () => void;
27+
/** 下一步回调 */
928
onNext: () => void;
29+
/** 播放/暂停切换回调 */
1030
onPlayPause: () => void;
31+
/** 跳转到指定步骤回调 */
1132
onSeek: (step: number) => void;
33+
/** 重置回调 */
1234
onReset: () => void;
1335
}
1436

37+
/**
38+
* ControlPanel 组件
39+
*
40+
* 功能:
41+
* - 显示播放控制按钮(重置、上一步、播放/暂停、下一步)
42+
* - 显示当前步骤和总步骤数
43+
* - 提供可点击/拖拽的进度条
44+
* - 支持键盘快捷键:左箭头(上一步)、右箭头(下一步)、空格(播放/暂停)
45+
*/
1546
export function ControlPanel({
1647
currentStep,
1748
totalSteps,
@@ -22,6 +53,7 @@ export function ControlPanel({
2253
onSeek,
2354
onReset,
2455
}: ControlPanelProps) {
56+
// 计算进度百分比,用于进度条显示
2557
const progress = totalSteps > 1 ? (currentStep / (totalSteps - 1)) * 100 : 0;
2658

2759
// 键盘快捷键

src/components/Header/Header.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
1+
/**
2+
* 页面头部组件
3+
*
4+
* 显示题目标题、LeetCode链接、GitHub源码链接和算法思路按钮
5+
*/
6+
17
import { useState } from 'react';
28
import { AlgorithmModal } from '../AlgorithmModal/AlgorithmModal';
39
import './Header.css';
410

11+
// LeetCode 题目链接
512
const LEETCODE_URL = 'https://leetcode.cn/problems/diameter-of-binary-tree/';
13+
// GitHub 源码仓库链接
614
const GITHUB_URL = 'https://github.com/fuck-algorithm/leetcode-543-diameter-of-binary-tree';
715

16+
/**
17+
* Header 组件
18+
*
19+
* 功能:
20+
* - 显示题目编号和标题,点击跳转到 LeetCode 原题
21+
* - 提供"算法思路"按钮,点击弹出算法讲解弹窗
22+
* - 提供 GitHub 源码链接
23+
*/
824
export function Header() {
25+
// 控制算法思路弹窗的显示状态
926
const [isModalOpen, setIsModalOpen] = useState(false);
1027

1128
return (

src/components/InputPanel/InputPanel.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,51 @@
1+
/**
2+
* 输入面板组件
3+
*
4+
* 提供二叉树数据的输入功能,包括:
5+
* - 手动输入数组格式的树数据
6+
* - 预设的样例数据快速选择
7+
* - 随机生成树数据
8+
*/
9+
110
import { useState } from 'react';
211
import { parseArrayInput, validateTreeArray, generateRandomTree } from '../../utils/treeUtils';
312
import './InputPanel.css';
413

14+
/**
15+
* InputPanel 组件的属性接口
16+
*/
517
interface InputPanelProps {
18+
/** 数据变化时的回调函数 */
619
onDataChange: (data: (number | null)[]) => void;
20+
/** 当前的树数据 */
721
currentData: (number | null)[];
822
}
923

24+
/**
25+
* 预设的样例数据
26+
* 包含多种典型的二叉树结构,方便用户快速测试
27+
*/
1028
const SAMPLE_DATA: { label: string; data: (number | null)[] }[] = [
11-
{ label: '示例1', data: [1, 2, 3, 4, 5] },
12-
{ label: '示例2', data: [1, 2] },
13-
{ label: '完全二叉树', data: [1, 2, 3, 4, 5, 6, 7] },
14-
{ label: '左斜树', data: [1, 2, null, 3, null, 4] },
15-
{ label: '右斜树', data: [1, null, 2, null, 3, null, 4] },
29+
{ label: '示例1', data: [1, 2, 3, 4, 5] }, // LeetCode 示例1
30+
{ label: '示例2', data: [1, 2] }, // LeetCode 示例2
31+
{ label: '完全二叉树', data: [1, 2, 3, 4, 5, 6, 7] }, // 完全二叉树
32+
{ label: '左斜树', data: [1, 2, null, 3, null, 4] }, // 只有左子节点的斜树
33+
{ label: '右斜树', data: [1, null, 2, null, 3, null, 4] }, // 只有右子节点的斜树
1634
];
1735

36+
/**
37+
* InputPanel 组件
38+
*
39+
* 功能:
40+
* - 提供输入框让用户输入 LeetCode 格式的数组
41+
* - 提供"应用"按钮验证并应用输入
42+
* - 提供"随机生成"按钮生成随机树
43+
* - 提供预设样例按钮快速切换不同的树结构
44+
*/
1845
export function InputPanel({ onDataChange, currentData }: InputPanelProps) {
46+
// 输入框的值
1947
const [inputValue, setInputValue] = useState(JSON.stringify(currentData));
48+
// 错误信息,用于显示输入验证失败的提示
2049
const [error, setError] = useState<string | null>(null);
2150

2251
const handleInputChange = (value: string) => {

0 commit comments

Comments
 (0)