用Python构建图论可视化认知系统:让离散数学变得直观

发布时间:2026/9/1 10:26:36
用Python构建图论可视化认知系统:让离散数学变得直观 简介本资源是一款面向高校学生、数学与计算机专业教师及图论初学者的Python可视化教学工具旨在解决图论概念抽象难懂、手绘演示低效、算法过程不可见等学习痛点。压缩包共63个文件总大小2.57MB包含23个核心Python脚本实现图的创建、遍历、最短路径、连通性分析等算法逻辑、5个Qt UI界面文件构建交互式图形操作环境、18个PNG与4个BMP图像用于图结构示例、图标与界面元素、1个GIF动画动态演示DFS/BFS遍历过程以及C扩展模块、多语言翻译文件和完整LICENSE与readme说明文档。已有348人学习下载资源结构清晰分层src目录组织主程序逻辑QtApp子项目封装GUI框架res目录管理资源test.py提供快速验证入口。用户可直接运行appMain.py启动系统通过拖拽节点、编辑权重、一键生成邻接矩阵、实时高亮算法路径等方式将抽象图论知识转化为可操作、可观察、可复现的可视化认知体验。 做这个系统其实源于我在课堂上的一次“翻车”。当时讲图论章节我在黑板上画了三个图的例子花了十分钟擦掉重画又花了五分钟底下的学生已经开始走神。课后有个学生来问老师最短路径算法到底是怎么一步步找到答案的我嘴上解释了半天最后发现最好的方式其实是让他亲眼看到搜索过程在图上蔓延的样子。从那时候起我就一直在想能不能用一个可视化认知系统把离散数学里最抽象的图论概念变成可以观察、可以交互的动态演示。于是就有了这个基于Python的离散数学可视化认知系统图论篇。这篇文章把整个设计与实现思路、核心源码结构、以及我踩过的坑都整理出来希望对正在自学图论、或者在准备相关课程设计的朋友有帮助。1. 系统整体设计与方案选型1.1 核心需求拆解可视化认知到底要解决什么问题先说我理解的“可视化认知系统”和普通演示程序的区别。普通的图论展示工具比如你在很多教学网站上看到的那种通常是预先画好一张图然后点按钮播放一个动画告诉你“这是广度优先搜索”。这种方案的问题在于学生看到的是一个已经排演好的结果而不是一个可以被提问、被改变、被重新探索的认知过程。我希望做出来的工具能让学习者主动去观察、去假设、去验证所以我的核心需求拆成三个层面第一层面是“看见”。图的结构要清晰展示出来节点、边、权重、方向都要直观。这听起来简单但实际做起来很讲究后面我会讲到布局算法的坑。第二层面是“动态”。算法的执行过程不是结果而是路径每一步访问了哪个节点、选择了哪条边、为什么这样选择都必须实时呈现。这要求可视化层和算法层解耦算法只负责计算可视化负责把计算过程翻译成人能理解的画面。第三层面是“可干预”。学习者能自己加节点、删边、改权值、切换算法然后立刻看到新结果。这个能力直接决定系统是“演示工具”还是“认知工具”。1.2 为什么选Python生态成熟度决定开发效率选Python做这件事可能有人觉得不新鲜但我自己对比之后确认这是最合理的选择。图论算法的经典实现C当然性能更好但做可视化认知系统核心价值不在性能而在“快速迭代算法版本”和“方便地对接可视化库”。Python的networkx就是专门为图论算法设计的库内置了Dijkstra、Floyd-Warshall、Kruskal、Prim等几乎所有经典算法的实现虽然我在系统里为了教学目的自己重写了这些算法以便展示内部过程但networkx作为基准测试工具和数据结构参考价值很大。而且Matplotlib作为可视化库虽然它不是专门做图可视化的但胜在可定制性强能做交互事件绑定。还有个优势是社区资料丰富遇到问题搜一下就有答案这点对新手尤其友好。对比一下其他方案JavaScript的D3.js做交互式图可视化确实很惊艳但算法逻辑得重新用JS写一遍数学专业的学生上手成本高。Java的JGraphT加GraphStream也成熟但环境配置相对繁琐。Python在这件事上是综合成本最低的选择。1.3 工具链与版本配套稳定的组合能省一半调试时间我开发时使用的关键工具和版本组合如下Python 3.103.8以上应该都行但我建议3.10及以上networkx 2.8.8核心图数据结构与拓扑操作Matplotlib 3.7.1图形渲染与交互事件处理numpy 1.24.3数值计算主要用于坐标变换和距离计算tkinter 8.6可选用于后期扩展的控制面板或者直接用Matplotlib内置的按钮组件安装方式就是常规的pip操作这里不赘述。但有一条经验值得分享如果是在Windows环境下开发建议用Anaconda管理环境避免numpy和Matplotlib的底层依赖冲突。我在项目初期就曾经因为pip装到了全局环境结果和已有项目版本冲突导致图形渲染出现诡异的中文乱码排查了半天才找到原因。2. 图数据结构设计与核心算法实现2.1 图的建模邻接表比邻接矩阵更适合教学展示图论算法的数据结构选型看起来是基础问题但直接影响后续的可视化逻辑。我在教学场景中同时支持三种图的类型无向图、有向图、加权图包括正权有向图。数据存储我选择了邻接表而非邻接矩阵。原因有两点第一邻接表在稀疏图场景下空间效率高。一张有50个节点、100条边的图用邻接矩阵需要2500个存储单元而邻接表只需要大约300个。可视化认知系统的教学案例大多是稀疏图邻接表的内存优势很明显。第二从教学角度邻接表更贴近人对“关系”的直觉理解。一个节点连接哪些邻居在邻接表里直接遍历list就行而在邻接矩阵里要扫描一行并逐个判断。所以在可视化过程中当算法在“遍历邻居”时用邻接表实现的搜索逻辑更容易和画面上的“扩散”形成对应关系。不过我也在系统里保留了邻接矩阵的转换函数为的是讲“普里姆算法”时可以用矩阵形式辅助理解“割”的概念。数据结构选型不是绝对的能灵活切换才是关键。我定义的图类核心方法如下class Graph: def __init__(self, directedFalse): self.directed directed self.adj {} # 邻接表{node: {neighbor: weight}} self.nodes set() def add_node(self, node): self.nodes.add(node) if node not in self.adj: self.adj[node] {} def add_edge(self, u, v, weight1): self.add_node(u) self.add_node(v) self.adj[u][v] weight if not self.directed: self.adj[v][u] weight def get_neighbors(self, node): return list(self.adj[node].keys()) def get_weight(self, u, v): return self.adj[u][v]2.2 遍历算法可视化BFS与DFS的队列栈原理对照图论的入门算法就是遍历广度优先搜索BFS和深度优先搜索DFS。这两个算法的可视化价值在于它们能极其直观地展示“队列”和“栈”这两种数据结构的行为差异。我实现BFS时使用显式队列每次从队首取出待访问节点将其未访问的邻居依次加入队尾DFS使用显式栈每次从栈顶弹出节点再将其邻居压入。用显式数据结构而不是递归是为了可视化时能直观看到“待访问列表”的内容变化。def bfs(self, start): visited set() queue deque([start]) visited.add(start) while queue: node queue.popleft() yield node # 通过yield逐步骤输出便于可视化 for neighbor in self.graph.get_neighbors(node): if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) def dfs(self, start): visited set() stack [start] while stack: node stack.pop() if node in visited: continue visited.add(node) yield node for neighbor in self.graph.get_neighbors(node): if neighbor not in visited: stack.append(neighbor)注意DFS这里我把邻居节点按某种顺序压栈如果希望展现出经典DFS的优先访问顺序需要逆序压栈或者给邻居排序。在实际的教学演示里我会特意把同一节点的邻居显示为一个栈的内容并在每一步高亮“当前待展开的栈顶元素”这样学生就能领会到“深挖到底再回溯”的含义。2.3 最短路径算法Dijkstra逐步松弛过程的视觉化拆解Dijkstra算法是图论中最重要的算法之一它的“贪心”思想和“松弛”操作非常值得可视化的精细刻画。我的实现版本将算法过程拆解为几个可观察的动作从优先队列中取出距离最小的节点标记当前处理节点遍历该节点的邻接边尝试松弛如果通过当前节点到达邻居的距离比已知距离短则更新距离表并记录前驱节点将更新后的节点重新放入优先队列为了让这个过程“透明”我把每一步都用一个生成器函数输出包含当前节点、被检查的边、距离是否更新等信息。可视化层消费这些信息将“正在处理中的节点”用高亮色标注“已确定最短路径的节点”用另一种颜色标记“正在检查的边”用粗线或动画闪烁呈现。def dijkstra(self, start): dist {node: float(inf) for node in self.graph.nodes} dist[start] 0 pq [(0, start)] prev {} confirmed set() # 已确定最短路径的节点 while pq: d, u heapq.heappop(pq) if u in confirmed: continue confirmed.add(u) yield {event: confirm, node: u, dist: d} for v in self.graph.get_neighbors(u): weight self.graph.get_weight(u, v) new_dist d weight if new_dist dist[v]: dist[v] new_dist prev[v] u heapq.heappush(pq, (new_dist, v)) yield {event: update, node: v, dist: new_dist, prev: u, edge: (u, v)}实际的效果是当动画播放时学生能清晰看到“已确定”节点如何从起点向外一圈一圈扩散就像水波一样。而“松弛”操作发生时被更新的节点会出现一个闪烁的数字变化这个设计让学生对“距离更新”的印象非常深刻。对于Floyd-Warshall算法我也单独做了矩阵视角的可视化模块右侧实时刷新的距离矩阵左侧对应的图结构当矩阵第i行第j列变化时图中对应边会高亮。这个设计对理解“中间节点”的递推关系帮助极大。2.4 最小生成树Kruskal与Prim的两个视角最小生成树也是图论的重要知识点。Kruskal算法按权重从小到大依次加边只要不形成环就保留。我实现的Kruskal可视化特别展示了“并查集”的有效性每次尝试加入一条边时如果该边连接的两个节点不在同一个集合中则颜色变绿表示接受否则变成红色并微微闪烁表示会形成环而被拒绝。class UnionFind: def __init__(self, nodes): self.parent {node: node for node in nodes} self.rank {node: 0 for node in nodes} def find(self, node): if self.parent[node] ! node: self.parent[node] self.find(self.parent[node]) return self.parent[node] def union(self, u, v): root_u, root_v self.find(u), self.find(v) if root_u root_v: return False if self.rank[root_u] self.rank[root_v]: self.parent[root_u] root_v elif self.rank[root_u] self.rank[root_v]: self.parent[root_v] root_u else: self.parent[root_v] root_u self.rank[root_u] 1 return TruePrim算法的可视化则走另一个路线从单个起始节点出发用一个优先级队列记录“当前最小可达边”每次取出一条权值最小的边如果连接的节点未被访问则将其加入生成树。和Dijkstra区别在于Prim更新的是“到已构建树的最小距离”Dijkstra更新的是“到起点的最短距离”。这句话理解透了两个算法就不容易混淆了。我在可视化界面上特别加了提示文本实时显示“当前已构建生成树的总权重”以及“候选边集合”帮助学习者从全局视角理解贪心策略的每一步。3. 可视化层设计与交互功能实现3.1 Matplotlib坐标体系与节点布局方案图可视化的基础是节点的坐标布局。这里我踩过不少坑分享一下经验。初期我直接按固定坐标放置节点效果很差——有的边交叉严重有的节点挤成一团图结构完全看不清。后来改用networkx内置的布局算法spring_layout模拟物理弹簧节点间存在斥力边存在拉力多次迭代后得到较稳定的布局。适合大多数无向图视觉效果自然。circular_layout所有节点等距分布在圆环上。适合展示Hamilton回路、欧拉回路等特定结构。shell_layout多圈层布局适合展示分层结构。kamada_kawai_layout基于最短路径的布局适合展示距离信息。系统默认采用spring_layout并固定随机种子以保证每次运行布局一致。布局结果输出为字典格式{节点: (x, y)}后续Matplotlib绘图时使用这些坐标。为了避免节点重叠我会根据节点度连接数调整节点大小——度数越高节点越大这样也能在视觉上传递一些图结构的信息。3.2 动画机制实现基于Matplotlib FuncAnimation的逐步调度动画的实现是可视化的重头。我的做法是算法生成器每产生一个步骤事件就将其转换成画面更新指令。有两种方案可选一是使用Matplotlib的FuncAnimation按固定时间间隔调用更新函数二是使用事件循环手动控制。我最终选择了FuncAnimation因为它与Matplotlib的交互组件按钮、键盘事件配合最好而且支持暂停/继续/重置等操作。核心实现逻辑是创建一个Figure和Axis规划第一个画面绘制所有节点和边在update函数中根据当前步骤的事件类型更新节点颜色、边粗细、文本内容通过plt.connect绑定按钮事件控制动画的播放和暂停动画的每一帧都使用clear后重绘的方式但这样性能较慢。优化方案是仅更新需要变化的artist对象节点散点图用set_color更新颜色边线条用set_linewidth更新宽度文本对象用set_text更新内容。这种做法可以有效保持交互流畅度即使图规模到100个节点也能跑得动。3.3 交互操作与事件绑定加节点、删边、修改权值系统还提供了手动编辑图结构的能力。通过Matplotlib的鼠标事件实现以下几种操作双击空白区域在该坐标添加新节点点击某个节点并拖动调整节点位置右键点击两个节点在它们之间添加一条边默认权重为1选中一条边并按Delete键删除该边双击某条边旁边的权值标签弹出输入框修改权值这些交互操作用到的核心事件是button_press_event、button_release_event、motion_notify_event和key_press_event。实现时要注意在事件回调中判断当前模式普通/添加边/删除避免不同操作之间的冲突。我花了比较多时间在“选中目标”的命中检测上——点击节点时需要计算鼠标坐标与所有节点坐标的距离选取最近且距离小于阈值的节点。这个阈值设置很关键太大会误触发太小则不易选中一般根据当前画布尺寸动态调整。4. 系统架构、完整源码结构与扩展指南4.1 模块划分算法引擎与可视化层彻底解耦系统的目录结构如下graph-visualizer/ ├── graph_model.py # 图数据结构 ├── algorithms.py # 图论算法实现BFS/DFS/Dijkstra/Floyd/Kruskal/Prim ├── visualizer.py # 可视化主程序Matplotlib绘制与交互 ├── animation_engine.py # 动画调度与事件管理 ├── utils.py # 布局计算、坐标转换等辅助函数 ├── config.py # 颜色、字体、尺寸等配置常量 └── main.py # 入口文件打开主界面为什么要做这么多文件因为如果算法逻辑和可视化逻辑混杂在一个文件里代码会很快变得不可维护。算法模块只负责计算并yield事件不关心任何绘图逻辑可视化模块只负责消费事件并更新画面。这个设计模式在软件开发里叫“生产者-消费者”在这里同样适用。当我想增加一个新的算法时只需在algorithms.py里写一个生成器函数并在可视化模块里添加对应的展示规则其他完全不用改动。可扩展性非常好。4.2 核心可视化类设计精讲class GraphVisualizer: def __init__(self, graph): self.graph graph self.pos self._compute_layout() self.fig, self.ax plt.subplots(figsize(10, 8)) self.node_scatter None self.edge_lines [] self.edge_labels {} self.anim None self.setup_events() def _compute_layout(self): if self.graph.directed: return nx.spring_layout(self.graph, seed42, k2.0) return nx.spring_layout(self.graph, seed42) def draw_static(self): 绘制初始图结构 self.ax.clear() # 绘制边 for u, v in self.graph.edges: x0, y0 self.pos[u] x1, y1 self.pos[v] line, self.ax.plot([x0, x1], [y0, y1], colorgray, linewidth1.5, zorder1) self.edge_lines.append(line) # 绘制节点 xs [self.pos[node][0] for node in self.graph.nodes] ys [self.pos[node][1] for node in self.graph.nodes] self.node_scatter self.ax.scatter(xs, ys, s300, clightblue, edgecolorsblack, zorder2) # 绘制节点标签和权值标签 self.ax.set_axis_off()这段代码的逻辑很直白但有三个细节值得注意。第一节点绘制使用scatter而不是plot因为scatter支持通过set_color一次性修改所有节点的颜色这对动画更新极其重要。第二zorder参数控制绘制顺序边在下方节点在上方防止节点被边覆盖导致点击困难。第三坐标布局的seed固定为42保证每次打开系统看到的图形一样——对教学应用来说这是必须的否则同一张图每次显示都不一样学生很难对照讨论。4.3 算法动画驱动的接口约定与事件流算法动画驱动的核心是“事件流”接口。每类算法都返回形如{event: 事件类型, ...其他字段}的字典流。可视化引擎根据事件类型执行不同的动画动作EVENT_HANDLERS { confirm: handle_confirm, # 高亮已确定节点 update: handle_update, # 更新节点距离和前驱 visit: handle_visit, # 访问节点 edge_accept: handle_edge_accept, # 接受边MST edge_reject: handle_edge_reject, # 拒绝边MST }这种设计的好处打个比方说算法像是发号施令的指挥官可视化像是执行命令的士兵。指挥官不需要知道士兵怎么画图士兵也不需要理解战略意图。它让整个系统非常容易调试和扩展。如果你要自己增加一个“着色算法”或“欧拉回路查找算法”只要遵守这个事件流接口改动量很小。4.4 完整源码获取与二次开发建议至于源码工程整个项目打包之后包含约2000多行Python代码。我建议拿到源码后先不要急着跑先读一遍graph_model.py和algorithms.py理解数据结构和算法输出的事件格式再去读visualizer.py这样看代码不容易迷失。运行入口在main.py运行命令是python main.py如果遇到Matplotlib版本不适配pip install -r requirements.txt可以解决依赖问题。二次开发方向上我特别推荐两个扩展方向一个是把算法事件流导出为JSON这样前端可以用D3或ECharts来做更华丽的网页版展示另一个是增加“自动出题”模式随机生成图并让学生先猜测最短路径然后再用动画验证。这个方向潜力很大我最近就在琢磨怎么结合搜索热词里常常出现的“可视化大屏”思路把系统做成可以嵌入Web的动画组件。5. 实战演示五个经典教学案例的运行效果5.1 案例一交通网络中的最短路径我构造了一个小型城市交通网络图节点代表城市边代表道路边权重代表行驶时间。启动Dijkstra算法起点设为A城市终点设为F城市。动画演示过程中学习者可以清楚看到算法不是直接“射向终点”的而是先探索起点附近的所有邻近城市再逐步扩展。当某条通往F的边被更新时路径上的边会变成醒目的橙色并显示当前累计距离。整个动画约15秒结束后自动显示完整最短路径和总距离。从这个案例出发学生很容易理解为什么Dijkstra不是一种“全局扫描”算法而是一种“局部贪心动态更新”的算法。5.2 案例二社交网络中的BFS层次扩散我用一个模拟的朋友关系图来演示BFS。从某个用户出发BFS按“朋友-朋友的朋友-朋友的朋友的朋友”逐层访问所有节点。可视化时不同的层级用不同的颜色表示第一层红色第二层橙色第三层黄色。这种色彩阶梯效果非常直观学生甚至不用看代码就能说出“BFS按距离分层访问”的核心特征。对照组用DFS跑同一张图颜色由深到浅随机变化学生立刻体会到“深挖”和“扩散”是两种截然不同的遍历策略。5.3 案例三Kruskal算法演示形成环的拒绝机制图中有六个节点九条边权值在1到10之间。Kruskal算法按权值排序后依次检查。观察动画时最有趣的部分是总权重最小的边连接两个节点时的那一帧。如果这条边会构成环动画会用红色闪烁显示它并弹出一行文字提示“用户注意这条边连接的两个节点已经属于同一个集合加入它会产生闭合回路因此算法拒绝它。”这个视觉反馈填补了纯文字教学中最难讲清楚的部分——什么叫“不能成环”。5.4 案例四Floyd算法矩阵同步变化Floyd算法的可视化界面分为左右两个区域。左侧是图右侧是距离矩阵。初始状态下矩阵中的直接相连边显示权值非直接相连显示∞。当动画进行到第k轮迭代时矩阵的第k行和第k列会被高亮随后根据“经由中间节点是否更短”的判断某些矩阵元素从大变小。我见过很多学生在看到这个画面时露出恍然大悟的表情——原来“中间节点”的含义是“允许使用的最大中转节点编号”。这种矩阵与图结构的同步联动展示比单纯讲递推公式有效得多。5.5 案例五欧拉回路的可视化验证欧拉回路的判定条件是“所有节点的度数均为偶数”。我在系统里增加了欧拉回路的查找与绘制功能。当用户导入一张图系统会自动判断是否存在欧拉回路如果存在则用动画的方式从某个节点出发依次经过每条边恰好一次然后回到起点。画面上已经走过一次的边会变成灰色未走过的边保持亮色这种“逐步消耗边”的视角能够清晰揭示欧拉回路的本质。对于不符合条件的图系统会高亮所有度为奇数的节点提示用户“至少要添加或删除某些边才能构造欧拉回路”。这个案例虽然不是传统的“最短路径”类算法但作为离散数学课堂的延伸非常受欢迎。6. 常见问题与排查技巧实录6.1 Matplotlib中文乱码问题Matplotlib默认字体不包含中文字符因此标题、按钮文字、权值标签在中文环境下会显示为小方块。解决方法是在config.py中添加如下设置import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, WenQuanYi Zen Hei] plt.rcParams[axes.unicode_minus] False但要注意不同操作系统可用的中文字体不同。Windows下SimHei或Microsoft YaHei都可以用Linux可能需要安装WenQuanYi字体。如果在Jupyter Notebook中运行有时还需要额外执行plt.rcParams[font.family] sans-serif才能生效。建议在系统启动时打印当前可用字体列表辅助排查。6.2 动画卡顿或响应延迟当图的规模较大超过80个节点时每帧全量重绘可能导致动画掉帧。排查思路有三个第一确认是否只更新必要的artist对象而不是清除整个axes后重绘。使用clear重绘的方案在50个节点的图上就会感觉到明显卡顿而局部更新可以轻松应对150个节点的图。第二检查布局算法是否在每次动画更新时被重复调用。布局计算是相对耗时的操作应该只在图结构改变时计算一次算法执行过程中保持节点坐标不变动画只改变颜色和线宽。第三适当降低动画帧率或者增大帧间隔。视觉上并不需要每帧都完全平滑1秒10帧左右的教学演示效果已经足够好。可以通过调整FuncAnimation的interval参数来控制我的默认值是200毫秒对于讲解场景偏慢但适合课堂播放。6.3 节点位置重叠导致无法点击spring_layout布局在某些特殊图结构下可能出现两个节点几乎完全重合的情况。此时用户点击时两个节点的命中检测都可能触发。我的解决方案是在布局计算完成后加入一个“抖动去重”步骤检测任意两个节点的距离是否小于阈值如果是则将该节点沿随机方向偏移一个小距离偏移量不超过节点半径的20%。这既能解决重叠问题又不会明显影响整体布局美观。另外在命中检测时优先返回距离最近的那个节点而不是返回所有匹配项。6.4 增强学习效果的几个演示技巧最后分享几个纯经验层面的技巧不算代码问题但很影响实际效果。第一个技巧是在动画开始时先把“算法伪代码”显示在画面侧边并在每一步高亮当前执行的行。这样学生同时看到代码和动画理解会深刻很多。我实现时用了一个文本框在学生模式中可以选择显示或隐藏。第二个技巧是给每个算法至少准备两个预设教学图。一个非常小方便看清细节另一个规模稍大用来展示算法在复杂图上的真实行为。比如Dijkstra我用过一个8节点图和一个20节点带权图效果都很好。第三个技巧是加入“慢动作”模式。正常速度播放一遍后用慢速重播重点观察某一步骤。视觉系统对快速变化的信息容易疲劳慢速重播能有效加深记忆。7. 从零实现图论可视化系统的扩容方向与个人心得如果有人想把这个系统扩展成一个完整的离散数学可视化认知平台图论篇之外还可以继续做“集合论与关系”、“树与二叉树”、“排列组合与生成函数”等篇章。就我目前体会图论章节是最适合可视化的因为图的结构天然有空间感。这些年教学下来我真切感受到一个直观点子抵得上一百句抽象解释这也是当初我搭这个系统的初衷。在编写整个系统的过程中最耗时还不是代码本身而是“想清楚每一个像素的变动到底传递了什么信息”。做可视化不能只是把结果画出来而要把“判断的理由”也画出来。比如Dijkstra中确认一个节点时我会同时显示当前已知的全部距离表这样学生才能看到“为什么现在确认这个节点是安全的”。这些细节才是一个认知系统区别于普通动画演示的核心。如果再给我一次重写机会我会在以下方面做得更好支持多图同时对比显示做“算法竞速”模式将动画导出为HTML5视频方便学生课后复习引入答题交互模块在动画结束后随机提问。也欢迎读过源码的朋友在实际使用中发现有意思的新需求随时交流。最后再说一个操作层面的小提示运行系统时如果发现边权值标签位置和边重叠不好看可以给每个权值标签添加一个偏移量依据边向量方向动态计算这样标签就不会骑在边上。类似的节点标签也可以稍微错开节点中心一点让画面更清晰。这些小的打磨细节往往才是演示效果好与不好的分水岭。本文还有配套的精品资源点击获取