Qt Widgets数独实战:从盘面绘制到回溯出题与位运算求解
简介这是一份基于Qt框架开发的数独游戏完整工程源码面向Qt初学者、计算机专业学生及游戏开发入门者提供从零搭建一个跨平台数独应用的完整参考。资源共141个文件压缩包约25.6MB包括21个C源文件、15个头文件、8个界面设计文件、47张图片素材以及工程配置和构建产物并区分debug与release目录方便直接查看运行效果与学习编译配置。已有2558人下载学习。项目围绕数独核心玩法展开涉及盘面随机生成、合法性校验、输入响应、进度保存等环节能够帮助读者深入理解Qt的信号与槽机制、QWidget与布局管理以及模型-视图-控制器结构在复杂界面中的实际应用。同时代码实现了自定义背景切换、规则说明与关于对话框并预留了多语言接口。在细节上工程使用QFile与QTextStream完成游戏存档的读写利用Q_ASSERT进行关键逻辑断言并通过QTranslator支持国际化这些都可作为实际项目开发的范本。整体目录清晰、模块划分明确适合一步步拆解学习也可在此基础上继续扩展难度选择、计时挑战或联机对战功能。1. 为什么用 Qt 写数独从画格子到出题判题的一整套练习很多人以为用 Qt 写数独游戏难点在界面得先把 9×9 的格子画漂亮。真正动手之后才发现出题、判题、唯一解校验才是绕不开的坎随机挖洞可能挖出多解盘面求解器如果写得慢点一下提示卡三秒。这篇文章把我用 Qt Widgets 实现完整数独游戏的一套做法写清楚覆盖盘面绘制、坐标映射、回溯出题、位运算求解、交互校验和性能验证。适合刚学完 Qt 基础、想拿一个小型但完整的项目巩固能力的开发者文中的代码按可编译、可扩展的方式组织照着敲完之后你会对数据层和界面层怎么分离有一个很直观的体感。2. 盘面绘制与交互坐标Widgets 选型、paintEvent 与鼠标映射2.1 技术选型Widgets 和 QML 怎么选数独游戏在界面上的需求其实很克制一个 9×9 的网格、数字文本、几个按钮。QML 的动画和粒子效果在这里基本派不上用场反而要花额外精力去维护 QObject 接口和模型映射。所以我一般直接选 Qt WidgetsQPainter 画网格非常直接鼠标事件和键盘事件的回调模型也清晰最重要的是算法逻辑可以纯 C 编写不依赖 QML 的 JavaScript 环境方便做单元测试。如果你把数独当作练手项目Widgets 还有一个隐性好处你能更多地把时间花在“如何组织数据结构和界面刷新”上而不是折腾 QML 的绑定语法。这里不评价 QML 好不好只说做一个桌面小游戏Widgets 是投入产出比更高的选择。界面层我通常拆成两个类一个叫SudokuBoard继承QWidget只负责画盘面、响应鼠标和按键另一个叫GameState负责维护 81 个格子的数据、撤销栈和校验逻辑。这样做的好处后面会反复体现界面上无论怎么改数据层不需要跟着动。2.2 用 QPainter 画出 9×9 盘面最小可运行代码先看SudokuBoard的绘制部分。核心思路是重写paintEvent用QPainter画网格线再用drawText在每个格子中央绘制数字。为了让窗口拉伸时盘面不变形我取width()和height()的较小值作为边长并居中绘制。// SudokuBoard.cpp void SudokuBoard::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); int side qMin(width(), height()); int offsetX (width() - side) / 2; int offsetY (height() - side) / 2; double cell side / 9.0; // 背景 painter.fillRect(offsetX, offsetY, side, side, QColor(#F7F8FA)); // 9x9 细线 QPen thin(QColor(#B0B7C3), 1); painter.setPen(thin); for (int i 0; i 9; i) { painter.drawLine(offsetX i * cell, offsetY, offsetX i * cell, offsetY side); painter.drawLine(offsetX, offsetY i * cell, offsetX side, offsetY i * cell); } // 3x3 粗线 QPen bold(QColor(#4A5568), 2); painter.setPen(bold); for (int i 0; i 3; i) { int pos i * 3 * cell; painter.drawLine(offsetX pos, offsetY, offsetX pos, offsetY side); painter.drawLine(offsetX, offsetY pos, offsetX side, offsetY pos); } }逻辑说明cell side / 9.0是关键如果写成side / 9整数除法最后一格的宽度会和前面不一致窗口较小时尤其明显。细线循环i 9画出 10 条线形成 9 个格子粗线循环i 3时pos取 0、3、6、9 格的边界正好构成 3×3 宫。数字绘制和格子高亮可以追加在同一个paintEvent里for (int row 0; row 9; row) { for (int col 0; col 9; col) { int idx row * 9 col; if (m_board[idx] 0) continue; QRectF rect(offsetX col * cell, offsetY row * cell, cell, cell); painter.drawText(rect, Qt::AlignCenter, QString::number(m_board[idx])); } }这里m_board是QVectorint长度 810 表示空格。为什么用一维数组而不是二维数组因为一维数组在递归回溯时可以少一层下标换算也方便用idx / 9和idx % 9在行列之间切换。窗口拉伸时盘面不会变形的保证来自两点一是边长的qMin二是居中偏移量offsetX/offsetY。如果直接把格子画在(0,0)到(width,height)窗口变成非正方形时格子会变扁这是新手最容易翻车的地方。2.3 鼠标点击坐标到行列的换算绘制时用的偏移量在鼠标事件里必须重新算一遍否则点击位置和格子对不上。我见过不少实现是直接event-pos()除以 9但窗口带了工具栏或边距时就开始串行。正确做法是保持和绘制完全一致的算法。void SudokuBoard::mousePressEvent(QMouseEvent *event) { int side qMin(width(), height()); int offsetX (width() - side) / 2; int offsetY (height() - side) / 2; QPoint pos event-pos(); // 点在盘面外直接忽略 if (pos.x() offsetX || pos.x() offsetX side || pos.y() offsetY || pos.y() offsetY side) { return; } int col (pos.x() - offsetX) / (side / 9.0); int row (pos.y() - offsetY) / (side / 9.0); if (row 0 row 9 col 0 col 9) { emit cellClicked(row, col); } }逻辑说明(side / 9.0)必须用浮点和绘制时的cell保持一致。如果pos.x()正好落在格子的边界线上整数除法会向下取整这通常是可接受的但如果你的需求要求边界精确可以加一个容差判断不过实际体验里几乎没人会刻意点一条线。鼠标事件里只发信号不做任何逻辑处理。逻辑层收到cellClicked(row, col)之后去更新高亮下标、显示候选数字、或者准备接收键盘输入。这样界面层和逻辑层的边界就非常干净。3. 数独生成算法从随机终盘到挖洞出题3.1 用回溯法生成完整终盘随机洗牌带来的唯一性差异出题的第一步是生成一个合法的完整终盘。最简单可靠的方式是回溯填充从第一个空格开始依次尝试数字 1 到 9用行、列、宫冲突检查做剪枝填满 81 格即完成。直接按 1 到 9 的顺序回溯每次运行都会生成完全一样的终盘所以必须对候选数字做随机洗牌。这里我用std::shuffle配合std::mt19937随机种子来自std::random_device保证每次启动的题目都不一样。// SudokuGenerator.cpp bool SudokuGenerator::fillBoard(QVectorint board, int idx) { if (idx 81) return true; int row idx / 9, col idx % 9; if (board[idx] ! 0) return fillBoard(board, idx 1); // 随机打乱 1~9避免生成固定终盘 QVectorint candidates {1, 2, 3, 4, 5, 6, 7, 8, 9}; std::shuffle(candidates.begin(), candidates.end(), m_rng); for (int num : candidates) { if (isValidAt(board, row, col, num)) { board[idx] num; if (fillBoard(board, idx 1)) return true; board[idx] 0; // 回溯恢复 } } return false; }isValidAt的实现是标准的三重检查同一行是否有相同数字、同一列是否有、所在 3×3 宫是否有。算法上可以只查已填入的位置不必扫描整个棋盘因为回溯过程中只会遇到 0 和已固定的数字。参数说明idx范围 0 到 80board用引用传递避免每次递归拷贝 81 个 int。这里有个优化点可以先预处理每一行的空白位置列表减少无效递归但对 9×9 数独来说回溯深度最多 81 层这个优化收益不大反而增加理解成本。如果你观察fillBoard的递归顺序会发现它总是从 0 开始逐个尝试随机性完全来自候选数字的顺序。这样既保证终盘合法又让不同次的生成结果差异足够大。关于随机种子记住一个原则桌面应用绝不要用qsrand(time(NULL))这种固定种子拼接因为同一秒内多次启动会拿到同一套题std::random_device在主流桌面平台上足够用。3.2 挖洞与难度控制怎么确认挖掉之后仍有唯一解拿到终盘之后出题就是“挖洞”把若干格子的数字清零同时保证剩余盘面只有唯一解。如果挖掉一个格子后出现多个解用户填到后面会发现同一个空位可以放两个数字都不冲突那题目就是废的。所以挖洞必须依赖求解器做校验。基本流程是随机打乱 81 个格子的访问顺序逐个尝试挖掉每次挖完调用“求解数量”函数只要解的数量大于 1 就回滚。这里我把“只找两个解”作为性能红线。bool SudokuGenerator::digHoles(QVectorint board, int targetHoles) { QVectorint cells(81); std::iota(cells.begin(), cells.end(), 0); // 随机顺序访问格子保证挖洞位置不固定 std::shuffle(cells.begin(), cells.end(), m_rng); int holes 0; for (int idx : cells) { if (holes targetHoles) break; if (board[idx] 0) continue; int saved board[idx]; board[idx] 0; int solCount 0; solveWithLimit(board, solCount, 2); if (solCount ! 1) { board[idx] saved; // 挖完变成多解撤销 } else { holes; } } // 不一定能挖到目标数量以实际结果为准 return holes targetHoles; }targetHoles的计算和难度绑定用“保留数字个数”来描述更直观。我给一个经常用的分档简单保留 40 到 45 个数字中等保留 36 到 39 个困难保留 30 到 34 个。保留越少空格越多挖洞过程中被回滚的概率越大生成耗时也越长。这里有个细节容易被忽视挖洞时随机访问的顺序会影响最终题面的形状。同样保留 32 个数字有的题目看起来“很散”有的题目某个宫全空。从可玩性角度散一点反而更好因为聚焦在一个宫里的空位会让用户感到无趣。如果你希望题目更均匀可以在随机洗牌后增加一个约束每个宫至少保留 2 个数字否则跳过该位置。生成耗时是这个环节的核心指标。若你的求解器是朴素回溯不剪枝地枚举所有解生成一道困难题可能耗时几百毫秒以上用后面第 4 章的位运算求解器通常可以压缩到 10 毫秒以内。4. 数独求解器回溯法和位运算的工程取舍4.1 求解器为什么是出题模块的“质检员”如果把出题流程比作生产线求解器就是质检员挖洞时每删一个数字都要问它“现在还有几个解”只有答案是“正好一个”才放行。所以在写生成器之前应该先把求解器写好而且要写成“传一个最多解数量限制”的版本而不是只返回“有没有解”。求解器的第二个用途是提示功能。用户点“提示”时取当前盘面让求解器算一个合法解把对应格子的数字显示出来。第三个用途是完成判定用户把所有空格填满时可以用求解器验算正确答案防止“填完但和标准答案不同”的情况。4.2 用位运算写一个 81 格的回溯求解器朴素回溯法的瓶颈在于每次尝试数字都要扫描行、列、宫导致递归里嵌套了三层循环。位运算的思路是用三个数组rowMask[9]、colMask[9]、boxMask[9]来记录已经占用的数字每个数字对应一个比特位。候选数字的集合就是这三个掩码的并集取反。// 递归前先初始化三个掩码 void initMasks(const QVectorint board, int rowMask[], int colMask[], int boxMask[]) { for (int i 0; i 9; i) { rowMask[i] 0; colMask[i] 0; boxMask[i] 0; } for (int i 0; i 81; i) { if (board[i] 0) continue; int r i / 9, c i % 9, b (r / 3) * 3 (c / 3); int bit 1 (board[i] - 1); rowMask[r] | bit; colMask[c] | bit; boxMask[b] | bit; } }主递归函数用 MRV 策略也就是优先处理候选数字最少的空格。这个优化对难题尤其重要能减少回溯分支数。bool solveWithLimit(QVectorint board, int count, int limit) { if (count limit) return true; // 已经找到足够多的解及时剪枝 int idx -1, minCnt 10; for (int i 0; i 81; i) { if (board[i] ! 0) continue; int r i / 9, c i % 9, b (r / 3) * 3 (c / 3); int used rowMask[r] | colMask[c] | boxMask[b]; int avail (~used) 0x1FF; // 只取低 9 位 int cnt countBits(avail); if (cnt minCnt) { minCnt cnt; idx i; } if (cnt 1) break; // 只有一个候选没必要再找 } if (idx -1) { count; return false; } // 没有空格找到一个解 int r idx / 9, c idx % 9, b (r / 3) * 3 (c / 3); int mask (~(rowMask[r] | colMask[c] | boxMask[b])) 0x1FF; while (mask) { int bit mask -mask; // 取最低位的 1 int num bitToNum(bit); board[idx] num; rowMask[r] | bit; colMask[c] | bit; boxMask[b] | bit; solveWithLimit(board, count, limit); // 回溯恢复 rowMask[r] ~bit; colMask[c] ~bit; boxMask[b] ~bit; board[idx] 0; if (count limit) return true; mask mask - 1; // 清除最低位的 1 } return false; }参数说明count是引用计数limit是求解数量上限。把limit设为 2就能判断唯一解设为 1就只验证“是否有解”。countBits可以用内置函数也可以手写 9 次循环因为只统计 9 位性能差异可以忽略。bitToNum把 1(num-1) 还原成数字 num用一个简单的 while 循环即可。这里要特别说明~used 0x1FF~used会把高位也置 1所以必须用0x1FF只保留低 9 位。很多人第一次写位运算求解器漏了掩码导致候选数字里出现大于 9 的位结果数字错乱排查很久。这是我的血泪经验。4.3 多解判断影响难度分级的关键细节digHoles里调用solveWithLimit(board, solCount, 2)solCount ! 1表示两种情况没有解或者有多个解。挖洞过程中不会出现没解的情况因为是从合法终盘挖下去的所以这里实际上只需要区分“1 个解”和“超过 1 个解”。为什么不直接数出所有解再判断因为多解盘面可能解的数量非常大全数完会浪费时间。solveWithLimit在count limit时提前返回所以绝大多数多解情况在找到第二个解时就停住了。这是出题性能的关键。limit参数的另一个用途是生成“提示数字”。当用户请求提示时可以用solveWithLimit(board, count, 1)快速算出任意一个解取当前空格的对应值写入。注意这里不要忽略状态恢复两条分支共享同一个board数组求解结束后必须恢复初始状态否则用户界面上的数字会错乱。5. 交互细节与常见问题排查输入校验、悔棋和高亮5.1 数据结构和界面分离别再在 paintEvent 里算数独我见过一个反面案例某位同事把棋盘状态直接存在SudokuBoard的成员变量里然后在paintEvent中调用算法函数判断当前玩家是否获胜。结果每次鼠标移动触发局部重绘时整个盘面要被扫描好几遍鼠标稍微快一点就掉帧。正确做法是单独建一个GameState界面类只保存一个数据指针绘制时只读取不计算。class GameState { public: QVectorint puzzle; // 当前盘面0 表示空格 QVectorint initial; // 出题时的初始盘面 QStackint undoStack; // 悔棋栈记录每次填入前的旧值 bool canEdit(int idx) const { return initial[idx] 0; // 初始数字不允许修改 } void setCell(int idx, int value) { if (!canEdit(idx) || puzzle[idx] value) return; undoStack.push(puzzle[idx]); // 保存旧值后悔药在这里 puzzle[idx] value; } void undo() { if (undoStack.isEmpty()) return; int idx undoStack.size() - 1; // 简化示意 // 实际应记录 idx 和旧值这里用一个栈存旧值 } };这里我把canEdit作为硬约束初始数字不可修改否则用户误触会把题目改掉。undoStack存的是“填入前的旧值”撤销时直接写回。如果同时记录格子下标两个栈同步操作功能更完整。逻辑层和界面层的接口就三四个信号cellClicked(row, col)、numberEntered(int num)、undoRequested()。界面只负责把用户动作翻译成信号数据层只负责改状态改完调用boardWidget-update()触发重绘。这样即使以后把 GUI 换成 QML算法层一行都不用动。5.2 输入校验、冲突高亮和“一步后悔药”输入校验常见的需求是用户填入数字后如果行、列或宫里有相同数字就把冲突格子和当前格子标红。最简单可靠的做法是填完之后做一次局部冲突检查而不是每次都调用求解器。求解器适合判胜负或提示不适合做实时反馈因为它在最坏情况下会走完整棵回溯树。// 检查 board 中 idx 位置的 num 是否与同行/同列/同宫冲突 bool isConflictAt(const QVectorint board, int idx, int num) { int r idx / 9, c idx % 9; int br (r / 3) * 3, bc (c / 3) * 3; for (int i 0; i 9; i) { // 同行 if (i ! c board[r * 9 i] num) return true; // 同列 if (i ! r board[i * 9 c] num) return true; // 同宫 int rr br i / 3, cc bc i % 3; if (rr ! r cc ! c board[rr * 9 cc] num) return true; } return false; }这里的board[idx]要先填入新数字再检查检查时要跳过自身下标否则必然会和自己冲突。如果你先检查再填值就少了跳过的逻辑但代码会更绕。我的习惯是填完检查然后通过update(rectOfCell)只重绘冲突的格子区域。冲突标红的绘制逻辑可以放在paintEvent里对每个格子如果它属于冲突集合就用浅红背景填充。注意这里要维护一个“当前冲突格子列表”成员变量由逻辑层在每次填入后计算并更新而不是在绘制函数里现算。悔棋功能是用户最依赖的“后悔药”。它不需要很复杂一个栈就够了。每次setCell时把旧值入栈undo()出栈并恢复。如果保存旧值的同时保存下标还能支持“撤销填错数字”但不撤销“擦除”的操作。很多小游戏把撤销栈设计成只存旧值结果撤销后光标位置丢失体验很差。我的建议是栈里存QPairint, int第一个是下标第二个是旧值。5.3 三个翻车现场坐标错位、窗口缩放变形和重绘卡顿先说坐标错位。现象鼠标点击一个格子高亮的却是它右边或下边的格子。原因大多是mousePressEvent里用了event-globalPos()然后忘记做窗口到控件的坐标映射。解决方法是直接用event-pos()它已经是控件本地坐标如果确实需要全局坐标用mapFromGlobal(event-globalPos())。另外窗口有边框或工具栏时全局坐标映射很容易出错所以优先用pos()。再说窗口缩放变形。现象把窗口拉宽后格子变成长方形数字不再居中。原因基本是绘制时没有用qMin(width(), height())做正方形约束。修法是回到第 2 章的绘制代码所有格子坐标都基于side和offset换算而不是基于width()或height()。记住一条绘制和鼠标事件里的坐标换算必须从同一个side导出。最后说重绘卡顿。现象鼠标在盘面上移动时CPU 占用飙升拖动窗口时盘面闪烁。原因有两种一是paintEvent里做了逻辑计算比如实时调用求解器二是每次mouseMoveEvent都触发update()导致整块盘面反复重绘。修法把计算逻辑全部移出绘制函数鼠标移动时只在“格子发生变化”时才update()。比如维护hoverRow和hoverCol内容变了才重绘。6. 性能验证与优化用 QElapsedTimer 确认生成耗时生成一道困难题到底要多久别用“感觉很快”来回答直接上QElapsedTimer量一下。我习惯写一个小的性能测试函数连续生成 10 盘记录最大耗时和平均耗时。QElapsedTimer timer; qint64 maxMs 0, sumMs 0; const int TESTS 10; SudokuGenerator gen; for (int i 0; i TESTS; i) { QVectorint board(81, 0); timer.restart(); gen.generate(board, 32); // 保留 32 个数字困难级别 qint64 ms timer.elapsed(); maxMs qMax(maxMs, ms); sumMs ms; } qDebug() avg: (sumMs / TESTS) ms max: maxMs ms;如果 max 超过 50 毫秒你该看一下是不是求解器没有用位运算或者digHoles的循环没有提前终止。事实上唯一解的校验在绝大多数情况下只递归几十层10 毫秒是一个很保守的目标。性能没问题之后还有一个体验细节值得做预生成题库。桌面小游戏启动时现场生成题目用户会看到几百毫秒的“转圈”。更好的做法是首次启动时生成 100 盘题存到本地 JSON 文件里之后每次启动随机抽一题。这样把生成耗时转移到后台用户无感知。最后一件事是教训。我在模拟项目X里做这个功能时忘记区分initial盘面和puzzle盘面结果“完成判定”把未填的空格也当成用户填过的格子直接判负。后来统一用GameState管理所有状态initial[idx] 0才允许修改这个问题才彻底消失。边界条件这种东西写的时候总觉得不会撞上但用户总会用一种你没想到的方式点到它。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

基于pdf.js的浏览器PDF阅读器插件开发:渲染、搜索高亮与交互实现

基于pdf.js的浏览器PDF阅读器插件开发:渲染、搜索高亮与交互实现

简介:一套基于 pdf.js 开发的在线文档处理插件,专为 Web 开发者提供可直接部署的 PDF 查看解决方案,核心功能包括全文搜索、结果高亮、区分大小写匹配、分页浏览、放大缩小、下载与打印,可通过 viewer.html?file 方式快速加载指定…

2026/10/14 16:33:43 阅读更多 →
AI 调一次工具,引擎在背后做了 9 件事

AI 调一次工具,引擎在背后做了 9 件事

目录先看全貌调用之前:先挡掉不该跑的调用前后:各拍一张快照调用之后:结果出门前实测:6 个调用,6 种结局写在最后在 AI 编程工具里,你在对话里看到的往往只有一行「调用了 edit」,几秒后结果就出…

2026/10/14 16:33:43 阅读更多 →
技术小团队管理实战:从程序员到组长的关键转型

技术小团队管理实战:从程序员到组长的关键转型

1. 基层技术团队管理的核心挑战带领4-5人技术小组是许多工程师晋升后的第一个管理岗位。这个角色既要做具体技术工作,又要协调团队产出,常常面临"既要写代码又要管人"的双重压力。我在某互联网公司带过3个不同技术小组,最深体会是&…

2026/10/14 16:33:43 阅读更多 →

最新新闻

TreeTycoon开发实战:从数据模型到经济平衡的完整指南

TreeTycoon开发实战:从数据模型到经济平衡的完整指南

简介:TreeTycoon 是一套基于 C17 与 SFML 开发的 2D 模拟经营游戏源码项目,面向具备一定 C 基础、希望研究游戏引擎架构与经营类玩法实现的开发者。项目围绕树木种植与经营主题,采用自定义游戏框架,涵盖场景栈管理、资源加载、音频…

2026/10/14 17:18:41 阅读更多 →
考研复试数据结构全攻略:机试面试高频考点与实战思路

考研复试数据结构全攻略:机试面试高频考点与实战思路

先说结论:考研复试里的数据结构,和初试完全是两码事。初试你可以在试卷上慢慢推演,复试要么是机试限时AC,要么是面试官盯着你手写代码、追问复杂度,你脑子里那点“背下来的知识点”根本不够用。这篇文章就是一次梳理&a…

2026/10/14 17:18:41 阅读更多 →
半导体金相显微镜核心技术与应用解析

半导体金相显微镜核心技术与应用解析

1. 金相显微镜在半导体行业的独特价值第一次接触半导体材料分析时,我被车间工程师的一句话点醒:"看得到缺陷才能解决缺陷"。金相显微镜正是半导体制造的"工业CT",它能将抽象的晶格缺陷、掺杂分布转化为可视化的微观图像。…

2026/10/14 17:18:41 阅读更多 →
Windows 安装 OpenClaw 常见问题,路径、杀毒、Gateway 离线排查

Windows 安装 OpenClaw 常见问题,路径、杀毒、Gateway 离线排查

Windows 一键部署 OpenClaw 教程|5 分钟搭建本地 AI 智能体,告别复杂环境配置 前言 2026 年开源圈爆火的「数字员工」OpenClaw(昵称小龙虾),GitHub 星标狂揽 28 万 。它区别于普通对话型 AI,是一套可以直…

2026/10/14 17:18:41 阅读更多 →
全栈记账系统开发实战:React+NestJS+PostgreSQL技术解析

全栈记账系统开发实战:React+NestJS+PostgreSQL技术解析

1. 项目背景与核心价值记账这件事,我从大学时代就开始折腾了。最早用Excel手工记录,后来试过各种记账App,但总感觉差点意思——要么功能太简单,要么数据不透明,要么就是突然收费。去年终于下定决心,自己开发…

2026/10/14 17:18:41 阅读更多 →
NSGA-II多目标优化MATLAB实现:从核心机制到避坑指南

NSGA-II多目标优化MATLAB实现:从核心机制到避坑指南

简介:这份资源是NSGA-II多目标优化算法的MATLAB实现代码,面向正在学习或研究多目标进化算法的本科生、研究生及科研人员,帮助读者理解非支配排序、拥挤距离、精英策略与基因操作等核心机制,并可直接用于测试函数实验。压缩包共27个…

2026/10/14 17:17:41 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →