用C++与Qt实现B树可视化:从分裂动画到调试器实战
简介这套项目压缩包以C与Qt完成的B树可视化工具为中心主要面向数据结构进阶学习者、Qt界面开发初学者以及需要课程设计参考的在校生。其中包含完整工程源码、可直接运行的演示程序以及配套资源文件能帮助读者理解B树作为一种自平衡多路搜索树的插入、删除、查找过程并掌握使用QGraphicsView画布、QPainter绘图、自定义图形节点构建交互界面的方法。包内共79个文件以cpp和h源码、exe可执行程序、dll动态运行库、qm翻译文件以及png演示截图为主压缩包大小约19.4MB解压后既可查看目录结构也能直接启动体验。已有632人学习下载。除了基础功能演示项目中还涉及鼠标点击与拖动处理、局部重绘优化、代码分层等实用技巧整体结构清晰适合对照源码逐步调试也适合作为B树算法可视化或Qt项目实战的参考资料。1. B树可视化为什么说画树比写树更难调试B树插入删除时对着日志看分裂合并看三遍就头大。我最初在某图像处理Demo里实现B树插入几百个随机键后某个节点分裂条件写错日志打印出来全是深度和key数量硬是看不出哪个子树的指针串了。后来花了一个晚上用Qt把B树画出来问题一眼就定位到某层某个节点多了一个子指针。这个经历让我意识到B树可视化不是花哨展示而是调试和教学场景里最高效的反馈工具。它解决两个问题一是把抽象的节点分裂、指针重连变成肉眼可见的结构变化二是让B树在插入、删除、查找时的路径变化可回放、可步进。适合谁正在用C实现B树的学生、想给数据结构课做演示工具的开发者、以及需要快速验证自研B树逻辑的工程师。核心思路很简单先用C把B树的数据结构做扎实再用Qt的QPainter把树画出来最后把操作过程变成动画。但真正动手后你会发现绘制节点只是冰山一角布局计算、状态同步、动画帧管理才是大头。2. 先搞定B树核心为什么可视化之前要先实现增删查2.1 B树的阶数与节点分裂规则可视化要用的关键数据B树的“阶”order决定了节点的容量。一个m阶B树每个节点最多有m-1个键最少有ceil(m/2)-1个键根节点除外。子节点指针数量为键数量加一。可视化前必须把这些规则映射成可绘制的数据每个节点内部有多少个键、是否有孩子、深度是多少、相邻兄弟是谁。我常用的B树节点结构会把keys和children分开存储并且用一个bool标记leaf。这样绘制时能直接知道该画几个键格子和几个连接线。插入时的分裂是可视化最核心的场景。当一个节点键数达到m时需要把中间键提升到父节点左右两部分成为两个子节点。这个“中间键提升”的动作在视觉上表现为一个节点被拆成三个部分左半、中间键、右半。删除时的合并与借键也是同理。可视化要做的不是画出最终状态而是把每一步从旧状态到新状态的过渡记录下来才能形成动画。2.2 最小C实现骨架节点结构、插入与分裂代码先写一个最精简的B树类只保留插入和分裂逻辑足够支撑可视化数据源。我用vector存储keys和children这样在分裂时可以用迭代器操作避免裸指针数组越界。struct BTreeNode { std::vectorint keys; // 节点内有序键 std::vectorBTreeNode* children; // 子节点指针 size keys.size() 1 bool leaf; // 是否叶子节点 explicit BTreeNode(bool isLeaf true) : leaf(isLeaf) {} }; class BTreeVisual { public: BTreeVisual(int order) : m_order(order), m_root(nullptr) {} void insert(int key) { if (!m_root) { m_root new BTreeNode(true); m_root-keys.push_back(key); return; } if (m_root-keys.size() m_order - 1) { // 根节点已满需要分裂 BTreeNode* newRoot new BTreeNode(false); newRoot-children.push_back(m_root); splitChild(newRoot, 0, m_root); m_root newRoot; } insertNonFull(m_root, key); } private: int m_order; BTreeNode* m_root; void insertNonFull(BTreeNode* node, int key) { int idx node-keys.size() - 1; if (node-leaf) { // 叶子直接插入使用upper_bound保持有序 auto it std::lower_bound(node-keys.begin(), node-keys.end(), key); node-keys.insert(it, key); } else { // 找到应该插入的子节点 while (idx 0 key node-keys[idx]) --idx; idx; if (node-children[idx]-keys.size() m_order - 1) { splitChild(node, idx, node-children[idx]); if (key node-keys[idx]) idx; } insertNonFull(node-children[idx], key); } } void splitChild(BTreeNode* parent, int childIdx, BTreeNode* child) { // 把child分裂成两个中间键提升到parent BTreeNode* right new BTreeNode(child-leaf); int mid m_order / 2; // 中间键位置m为偶数时取右中位 right-keys.assign(child-keys.begin() mid 1, child-keys.end()); if (!child-leaf) { right-children.assign(child-children.begin() mid 1, child-children.end()); } parent-keys.insert(parent-keys.begin() childIdx, child-keys[mid]); parent-children.insert(parent-children.begin() childIdx 1, right); child-keys.resize(mid); if (!child-leaf) child-children.resize(mid 1); } };这段代码里分裂时取mid order/2。如果order是偶数B树有两种分裂策略取左中位还是右中位。这里取右中位分裂后左节点有mid个键右节点有order - mid - 1个键。对于order4左节点2个键右节点1个键符合最少ceil(4/2)-11的要求。实际中很多实现为了简化用mid (order - 1) / 2你能看到右节点会多一个键。可视化时需要注意分裂后的左右两个节点键数可能不对称画图时要允许不同宽度的节点。这个类没有删除操作因为删除的合并和借键逻辑比较复杂且不是可视化最初要解决的问题。我一般先只做插入和查找把结构画出来确认插入分裂正确后再考虑删除的动画。删除的可视化关键在于合并和借键的步骤后面的动画章节会谈到如何用快照机制覆盖这些操作。2.3 为什么不能直接把递归过程画出来状态与动画的分离我第一次做可视化时直接在insert函数里调用update()重绘以为递归每走一步就会更新界面。结果界面几乎不刷新因为递归过程跑得极快所有修改在几毫秒内完成用户根本看不到中间过程。更麻烦的是如果在递归中途调用repaint()可能导致重入、死锁或者界面卡死。正确做法是把B树的“操作过程”记录为一系列“状态快照”每个快照对应一次关键节点改动后的树结构。绘制层只负责渲染当前快照。动画层用定时器在快照之间切换产生逐步演变的视觉效果。这样数据层和显示层完全解耦调试时还可以拖一个滑动条手动查看每一步。实现思路是定义一个TreeSnapshot结构保存节点的坐标、键值、子节点深度。当插入操作完成后我们收集所有分裂事件每个分裂事件结束后生成一次快照。这个快照列表就是动画帧。QTimer每Tick一格更新当前帧并重绘。这种方式不仅适配插入也能适配删除、查找只要算法层在“关键改动”后调用一个m_snapshots.push_back(copyTree())即可。3. Qt绘制B树的坐标布局从根节点到叶子节点3.1 计算每个节点的矩形坐标层次遍历与宽度分配画B树不像画二叉树每个节点内部有多个键宽度不固定。常见做法是先用递归后序遍历算出每棵子树需要的总宽度然后按层次分配水平坐标。具体规则叶子节点的宽度等于它自身键数量对应的矩形宽度内部节点的宽度等于所有子节点宽度之和加上自己的键所占宽度。这样整棵树不会出现子树重叠。我常用一个layoutNode函数返回节点的水平偏移量并在递归中记录每个节点的中心x坐标和所在深度y。struct NodeLayout { double x; // 节点中心x坐标相对整棵树 int depth; // 深度用于计算y double width; // 本节点占据的总宽度 BTreeNode* node; // 对应B树节点 NodeLayout* parent; std::vectorNodeLayout* children; }; double computeWidth(BTreeNode* node, int depth, NodeLayout* layout, double cursor) { layout-depth depth; if (node-leaf) { double w node-keys.size() * NODE_SLOT_W NODE_PADDING; layout-x cursor w / 2.0; cursor w LEVEL_GAP; layout-width w; return w; } else { double xLeft cursor; double totalChildW 0; for (auto* child : node-children) { NodeLayout* childLayout new NodeLayout(); layout-children.push_back(childLayout); totalChildW computeWidth(child, depth 1, childLayout, cursor); } double selfW node-keys.size() * NODE_SLOT_W NODE_PADDING; double totalW std::max(selfW, totalChildW); layout-x xLeft totalChildW / 2.0; layout-width totalW; cursor xLeft totalW; return totalW; } }这个算法的关键是游标cursor。叶子节点从左到右依次分配空间内部节点把子树的中心作为自己的中心所以父节点一定落在所有子节点的几何中心上。这样画出来的树是平衡的视觉结构。但要注意如果某个内部节点的自身键数特别多、子节点特别窄会出现节点矩形覆盖子节点矩形。解决办法是取totalW std::max(selfW, totalChildW)并让节点中心对齐子树的中心。实际效果是宽度小的那一方居中不会重叠。3.2 用QPainter绘制节点与连线最小绘制Demo有了坐标绘制就简单了。重写QWidget的paintEvent遍历布局树先画连线再画节点矩形。画连线要注意从父节点底部中心到子节点顶部中心。如果节点内部有多个键连线的起点应该对应具体的子指针位置而不是笼统的底部中心。void TreeWidget::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.translate(m_scrollX, m_scrollY); // 滚动偏移 // 先画所有连线 drawEdges(m_layout, painter); // 再画所有节点 drawNodes(m_layout, painter); } void TreeWidget::drawEdges(NodeLayout* node, QPainter* painter) { if (!node) return; QPointF parentBottom(node-x, node-depth * LEVEL_H NODE_H); for (auto* child : node-children) { QPointF childTop(child-x, child-depth * LEVEL_H); painter-drawLine(parentBottom, childTop); drawEdges(child, painter); } } void TreeWidget::drawNodes(NodeLayout* node, QPainter* painter) { if (!node) return; double w node-node-keys.size() * NODE_SLOT_W NODE_PADDING; QRectF rect(node-x - w/2, node-depth * LEVEL_H, w, NODE_H); painter-setBrush(QColor(#f8f8f8)); painter-setPen(QPen(QColor(#333333), 2)); painter-drawRoundedRect(rect, 6, 6); // 在矩形内画每个键 for (size_t i 0; i node-node-keys.size(); i) { QRectF keyRect(rect.left() NODE_PADDING/2 i * NODE_SLOT_W, rect.top(), NODE_SLOT_W, NODE_H); painter-drawText(keyRect, Qt::AlignCenter, QString::number(node-node-keys[i])); } for (auto* child : node-children) { drawNodes(child, painter); } }这个绘制Demo里NODE_SLOT_W是一个键槽的宽度NODE_H是节点高度LEVEL_H是层高。这些参数在绘制前定好通常在初始化里设置。关键在于drawNodes递归时每个节点先画自身矩形再画键值最后递归画孩子。这样顺序保证孩子覆盖在连线之上视觉上更干净。实际中paintEvent里要判断m_layout是否为空避免第一次显示时崩溃。3.3 处理节点内多个键的绘制单元格划分与高亮B树节点往往不止一个键绘制时要把矩形内部均匀划分成多个单元格。每个单元格显示一个键值。我一般用QFontMetrics提前计算键值字符串的宽度如果比NODE_SLOT_W宽就缩小字体或者截断显示。对于可视化来说键值都是数字所以这个问题不大。但如果键是字符串就需要动态调整键槽宽度。QFontMetrics fm(painter.font()); int maxKeyW 0; for (int key : node-node-keys) { int w fm.horizontalAdvance(QString::number(key)); maxKeyW std::max(maxKeyW, w); } double slotW std::max(NODE_SLOT_W, maxKeyW 8);高亮是可视化里重要的交互反馈。比如查找时当前访问到的节点用黄色背景要找的键落在哪个键槽里用橙色边框。实现方式是在NodeLayout里加一个枚举状态Normal、Visited、Matched、Split。绘制时根据状态选择brush和pen。这一步看似简单但它决定了可视化的“教学”价值——用户能清楚知道算法每一步走到了哪里。4. 把操作变成动画插入、删除的逐步可视化4.1 用QTimer驱动动画帧避免阻塞UI线程动画的本质是切换快照。我最开始尝试在每个操作间加QThread::sleep(100)结果UI完全卡死。正确做法是用QTimer按固定间隔触发update()每次更新时改变“当前帧索引”。class BTreeWidget : public QWidget { Q_OBJECT public: void startAnimation(const QVectorTreeSnapshot snapshots) { m_snapshots snapshots; m_curFrame 0; m_timer-start(120); // 每120ms切换一帧 } private slots: void onTick() { if (m_curFrame m_snapshots.size() - 1) { m_timer-stop(); return; } m_curFrame; m_layout buildLayout(m_snapshots[m_curFrame].root); update(); } };QTimer的间隔就是每帧停留时间。间隔太短50ms会让眼睛跟不上太长300ms会显得拖沓。插入操作一帧通常对应一次节点分裂或一次键插入120ms比较合适。如果要慢速演示可以把间隔设为250ms并用一个滑动条动态改变这个值。4.2 记录操作快照序列从插入到分裂的帧数据快照不是把整棵树拷贝一份那样内存爆炸而是把每次关键改动后的“布局所需数据”存下来。由于B树节点指针是动态的拷贝整棵节点树最稳妥但代价高。我常用做法是在插入算法内部每当发生一次分裂或插入就调用cloneTree(m_root)生成一个新根指针并存入快照列表。为了控制内存每个快照只保存相同深度下的节点键值和子指针关系而不是整棵树的完整绘制缓存。struct TreeSnapshot { std::shared_ptrBTreeNode rootClone; // 智能指针避免泄漏 QString operation; // 操作说明比如“插入7后分裂” }; std::shared_ptrBTreeNode cloneTree(BTreeNode* node) { auto copy std::make_sharedBTreeNode(node-leaf); copy-keys node-keys; copy-children.reserve(node-children.size()); for (auto* child : node-children) { copy-children.push_back(cloneTree(child).get()); // 混用原始指针仅为了演示 } return copy; }这里混用shared_ptr和原始指针是临时做法实际工程里建议全部使用智能指针或在BTreeNode里用std::vectorstd::unique_ptrBTreeNode。快照序列的长度等于插入过程中调用cloneTree的次数。有一点要特别注意如果插入过程没有发生分裂那至少也要在最终插入完成后存一次快照否则动画没有终点。记录快照的位置通常在insertNonFull函数里每当splitChild执行完毕或者叶子插入完成后追加一次快照。这样可以精确表现“插入键”和“分裂提升”两个动作。4.3 动画参数时长、缓动、暂停与步进控制动画不是简单的帧切换很多细节会影响体验。首先是缓动直接跳变会让节点位置突变看起来像抖动。我习惯在每帧之间做线性插值。比如一个节点分裂前后左右两个新节点从旧位置平滑移动到新位置。实现方式是为每个节点在快照中记录一个“目标矩形”当前帧绘制时根据插值t计算出实际矩形。double t (m_curFrame - m_snapshots[m_curFrame - 1].frameTime) * 0.3; // 0~1 if (t 1.0) t 1.0; double x oldRect.x() (newRect.x() - oldRect.x()) * t; double y oldRect.y() (newRect.y() - oldRect.y()) * t;这一行就是动画的灵魂。在实际项目中我会在NodeLayout里保存oldRect和newRectpaintEvent根据t进行插值绘制。这样即使节点数量很多视觉上也是平滑展开的而不是跳变。暂停和步进控制是可视化工具必备的。我用空格键播放/暂停左右方向键步进。实现时只需要控制QTimer::start()和stop()步进时手动m_curFrame并update()。还有一个细节当动画播放到最后要允许用户回退到上一帧。所以我在m_curFrame减小时同样要重新buildLayout。这里的构建布局开销不大因为B树节点数量通常很少几百个键就很高了。5. B树可视化避坑指南坐标溢出、闪烁、布局抖动5.1 节点数量多时窗口滚动与缩放用QScrollArea还是自绘坐标变换现象插入几百个随机键后整棵树宽度超过窗口右侧和底部的节点看不见。原因QWidget默认大小不会随布局自动增长paintEvent里坐标超出widget范围就被裁剪。解决两种方案。第一种是把B树绘制放在一个自定义QWidget里设置它的minimumSize为布局的总宽度和总高度然后外层套一个QScrollArea。这种方案最简单但缩放时字体会跟着变大变小不灵活。第二种是我常用的在paintEvent里自己处理坐标变换通过painter.translate()和painter.scale()实现缩放和平移同时用滚轮事件控制scale因子。我推荐第二种因为可视化交互中经常要放大看某个局部自绘坐标变换可以同时支持缩放和拖拽平移。void TreeWidget::wheelEvent(QWheelEvent* event) { double factor (event-angleDelta().y() 0) ? 1.1 : 0.9; m_scale * factor; m_scale qBound(0.2, m_scale, 3.0); update(); }注意缩放中心默认是窗口左上角这样放大时焦点会跑偏。最好以鼠标位置为中心缩放这需要调整translate参数公式是偏移 鼠标位置 - (鼠标位置 - 原偏移) * (新缩放/原缩放)。这个细节直接影响手感我当年没写对放大时树一直往右下角跑很折磨。5.2 刷新闪烁问题双缓冲与update的正确用法现象动画播放时树形闪烁尤其绘制大量连线时更明显。原因paintEvent里直接调用了painter.eraseRect()或者没有启用Qt默认的双缓冲也可能是每次update都触发了整个widget重绘而布局构建又要遍历整棵树。解决Qt的QWidget默认开启双缓冲但前提是不要手动调用repaint()。我在动画帧切换时只调用update()它会把重绘合并到事件循环里避免高频刷新。另一个问题是布局构建在paintEvent里做每次绘制都递归一遍代价不小。正确做法是把buildLayout放在动画帧切换时执行paintEvent里只读取已经构建好的布局数据。如果仍然闪烁检查是否在构造函数里设置了setAttribute(Qt::WA_OpaquePaintEvent)这个属性会在没有背景填充时引发闪烁要去掉或改为false。5.3 删除节点后布局抖动稳定布局算法与固定层高现象删除一些键后整个树看起来“跳”了一下节点左右位置和之前完全不一样难以跟踪。原因布局算法完全取决于当前树的结构删除后某些子树宽度变化导致整棵树重新分配水平坐标节点移动了很大距离。解决布局稳定性是可视化中相对进阶的问题。我采用的简单办法是固定“层高”和“基础网格步长”让每个叶子节点始终占一个整数倍的水平单元内部节点的中心尽可能落在子树的中间但不过分偏移。更直接的做法是把布局的cursor从0开始改成从根节点的中心开始根节点居中对齐然后递归只计算相对偏移不强制所有叶子从左到右紧密排列。这样删除时多数节点只会小幅度移动不会全局重排。void stableLayout(NodeLayout* node, double parentX, int depth) { node-depth depth; double childStartX parentX - node-width / 2.0; double childOffset 0; for (size_t i 0; i node-children.size(); i) { auto* child node-children[i]; double x childStartX childOffset child-width / 2.0; stableLayout(child, x, depth 1); childOffset child-width LEVEL_GAP; } }这个版本在根节点调用时传入根中心x子节点在父节点宽度范围内排列。即使删除导致宽度变化根节点位置固定视觉抖动会小很多。但要注意如果节点数量很多宽度累计超过窗口仍需要滚动。5.4 中文与字体渲染问题QFontMetrics计算宽度现象节点键值如果是中文字符串矩形宽度经常不够文字被截断或重叠。原因默认字体宽度对全角字符的计算不准或者我直接用NODE_SLOT_W固定宽度没有考虑字体度量。解决在绘制前用QFontMetrics::horizontalAdvance计算所有键值的最大宽度动态调整节点矩形宽度。注意必须使用同一个QFont实例避免字体切换后度量不一致。另一个坑是Qt默认字体下中文渲染可能比较窄最好在控件初始化时显式设置QFont(Microsoft YaHei, 10)或系统默认支持中文的字体。这里不涉及任何外部依赖只是Qt运行时设置。6. 进阶把可视化做成可交互的B树调试器当基础的插入、删除动画跑通后我会把工具升级成“调试器”而不是“演示器”。这个阶段有几个非常实用的功能。第一个是“随机插入批量数据”比如点击按钮一次插入100个随机数动画自动播放我可以在播放过程中暂停并检查每一步分裂是否符合B树定义。这比单步手动插入高效得多。第二个是“查找路径高亮”输入一个键动画只展示查找过程中访问的节点把非路径节点变灰。实现上是在快照里记录每个节点的状态枚举查找算法每访问一个节点就更新一次快照。第三个功能是导出当前树为PNG图片。调用widget-grab().save(btree.png)即可但要注意先滚动到适当位置或者临时把窗口设置成整棵树的尺寸。导出图片对写实验报告、做教学PPT很有用。第四个是“B树完整校验”校验每个节点的键数量范围、父子节点键值顺序是否正确。把它作为一个菜单动作配合可视化能快速发现插入逻辑的边界问题。我记得有一次用这个调试器跑删除操作发现删除后某节点的父节点键数变成0但根节点不能为空。这个锅是在删除合并时没判断父节点是否就是根。当时可视化界面直接显示根节点是一个空矩形非常扎眼比看日志舒服多了。这个经历让我养成了一个习惯任何B树操作做完都先跑一遍校验函数再更新动画快照。校验函数也不复杂递归检查每个节点的keys.size()是否在合法范围内并检查所有子树指针是否为空。可视化加上自动校验才是真正的调试器而不是一个花哨的动画。最后一个实用技巧是给每个节点添加“操作日志气泡”当节点发生分裂或合并时在节点旁边短暂显示一个文本提示。这个气泡可以用一个独立的QElapsedTimer控制显示时间在paintEvent里根据剩余时间决定是否绘制。做起来不复杂但能极大降低用户理解每一步的门槛。我现在的B树可视化工具已经稳定跑了一年多每次写B树变体时都会先画一画再写逻辑血泪经验就是先看到树再相信算法。希望这些坐标布局、动画帧、避坑处理能帮到正在做B树可视化的你。本文还有配套的精品资源点击获取

相关新闻

Gemini CLI 也能跑?logo-design-skill 不只是 Claude 专属:双 CLI 上手指测

Gemini CLI 也能跑?logo-design-skill 不只是 Claude 专属:双 CLI 上手指测

Gemini CLI 也能跑?logo-design-skill 不只是 Claude 专属:双 CLI 上手指测 【免费下载链接】logo-design-skill A comprehensive logo-design skill for Claude, Gemini CLI, Codex and other AI agents: principles, process, SVG craft, testing tool…

2026/10/12 10:28:59 阅读更多 →
Trae + IDEA:构建可管控、可审计、可扩展的团队级AI协作基座

Trae + IDEA:构建可管控、可审计、可扩展的团队级AI协作基座

1. 为什么“告别 Cursor 收费墙”成了高频搜索词?——从真实协作断点切入最近两周,我在三个不同技术群和两个内部协作频道里,反复看到类似提问:“Cursor 免费版突然卡在登录页”“团队新成员装不上 Copilot 插件”“昨天还能用的智…

2026/10/12 10:29:03 阅读更多 →
SpringBoot2+Vue3+MyBatis-Plus馆藏管理系统全栈开发实践

SpringBoot2+Vue3+MyBatis-Plus馆藏管理系统全栈开发实践

博物馆、档案馆、文化馆这类单位的信息化项目,我做下来觉得最核心的问题不是技术本身,而是“台账怎么电子化才不乱”。这里分享一个基于 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 构建的线上历史馆藏系统,整套源码我近期整理过&#xff0c…

2026/10/12 10:29:11 阅读更多 →

最新新闻

基于SSM+Vue的流浪动物领养助养系统毕业设计全流程解析

基于SSM+Vue的流浪动物领养助养系统毕业设计全流程解析

去年秋招失败后的那段时间,我每天从学校回出租屋都要绕路经过一家流浪动物救助站。救助站的负责人是个四十多岁的大姐,她给我看了三本厚得像字典的纸质登记册,里面密密麻麻写着编号、转账记录、领养人电话。她说他们最缺的不是爱心&#xff0…

2026/10/12 15:03:44 阅读更多 →
微信官方 Bot API 开放后,ClawBot 插件如何用 TaoToken 统一 Key 接入 AI 能力

微信官方 Bot API 开放后,ClawBot 插件如何用 TaoToken 统一 Key 接入 AI 能力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 15:03:44 阅读更多 →
OpenClaw 正在重塑我的数字生活:从 SaaS 退订到 Mac Mini 本地推理的 TaoToken 实践

OpenClaw 正在重塑我的数字生活:从 SaaS 退订到 Mac Mini 本地推理的 TaoToken 实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 15:03:44 阅读更多 →
HyperFrames 爆火但别神化:确定性渲染救不了没创意,HTML 视频框架的边界到底在哪

HyperFrames 爆火但别神化:确定性渲染救不了没创意,HTML 视频框架的边界到底在哪

HyperFrames 爆火但别神化:确定性渲染救不了没创意,HTML 视频框架的边界到底在哪 【免费下载链接】hyperframes Write HTML. Render video. Built for agents. 项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes HyperFrames 火了。…

2026/10/12 15:03:44 阅读更多 →
Azkaban solo-server 0.1.0-SNAPSHOT 部署与工作流调度实战

Azkaban solo-server 0.1.0-SNAPSHOT 部署与工作流调度实战

简介:本资源为Azkaban单服务器版(Solo Server)0.1.0-SNAPSHOT的已编译tar.gz包,面向需要快速搭建工作流调度环境的小型团队、个人开发者及大数据初学者。Azkaban由LinkedIn开源,用于简化Hadoop作业调度,提供…

2026/10/12 15:03:44 阅读更多 →
编译原理实验一词法分析器:从状态图到Token识别的全解析

编译原理实验一词法分析器:从状态图到Token识别的全解析

简介:北邮编译原理课程词法分析器实验的完整源码包,面向正在学习编译原理的本科生,以编译器前端的关键环节为切入点,帮助理解扫描器如何将源代码字符流拆分为记号,并识别关键字、标识符、常量、运算符等词素。包内共4个…

2026/10/12 15:02:42 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →