2026/10/26 10:34:18

四子棋AI面试通关指南:从状态表示到Alpha-Beta剪枝的完整链路

四子棋AI面试通关指南:从状态表示到Alpha-Beta剪枝的完整链路 简介这份资源是一套重力四子棋人机对弈程序面向对博弈树搜索与启发式评估感兴趣的学习者可用于理解棋类 AI 的决策流程与剪枝优化思路。压缩包共 5 个文件以 3 个 .h 头文件和 2 个 .cpp 源文件为主头文件承担棋盘、落子点与策略接口的声明源文件实现裁判逻辑与搜索评估主体整体约 7KB体量轻巧便于通读与二次修改。程序核心采用 α-β 剪枝配合较完善的估价算法在有限搜索深度下兼顾走子质量与响应速度读者可据此观察剪枝顺序、估值函数权重对棋力的影响并尝试替换评估维度或调整搜索策略做对比实验。目前已有 1221 人学习下载适合作为人工智能课程设计、博弈算法入门练习或小型对弈项目的参考实现。1. 四子棋 AI 面试从“能下赢”到“讲得清”的分水岭四子棋对抗 AI 这类题面试官真正想看的不是你能不能写出一段能跑的代码而是你能不能把一个博弈问题拆成状态、评估、搜索三层并且说清楚每一层为什么这么设计。很多人栽在“我调了参数就赢了”这句话上——赢了棋输了面试。重力四子棋的规则简单到三句话能讲完但它的状态空间、剪枝策略、评估函数设计恰好能把一个候选人的算法直觉和工程取舍暴露得干干净净。这场面试考什么考的是你对极小化极大搜索的理解深度、对评估函数权重的敏感度、对搜索深度与性能平衡的判断力。适合谁适合已经写过基础博弈程序、但一被追问“为什么用这个启发式”就卡壳的人。拿分的关键不在代码量在于你能不能用面试官听得懂的语言把每一步决策的代价和收益讲明白。2. 考点分布重力四子棋 AI 的三个必考模块2.1 状态表示与落子合法性棋盘怎么存、重力怎么模拟重力四子棋和普通四子棋最大的区别在于落子受重力约束棋子落在某一列时会落到该列最底部的空位。这个规则看似简单却是面试第一道筛子。面试官通常会先让你描述棋盘的数据结构再追问落子合法性判断的复杂度。最直接的表示是二维数组行从上到下、列从左到右空位用 0一方用 1另一方用 2。落子时从该列最底行往上找第一个空位。这类题通常这么答先给数据结构再给落子函数最后主动提一句“列满时该列不可选生成合法走法时要跳过”。面试官最想听的是你有没有把“合法走法生成”和“落子”分开——很多考生把两者揉在一起导致搜索树里出现非法分支。# 棋盘用二维列表表示行 0 在最上方行 ROWS-1 在最下方 ROWS, COLS 6, 7 EMPTY, PLAYER, AI 0, 1, 2 def get_legal_moves(board): 返回所有未满列的索引列满则跳过 moves [] for c in range(COLS): if board[0][c] EMPTY: # 最顶行空说明该列未满 moves.append(c) return moves def drop_piece(board, col, piece): 在指定列落子返回落子后的行号列满返回 -1 for r in range(ROWS - 1, -1, -1): if board[r][col] EMPTY: board[r][col] piece return r return -1这段代码的关键在get_legal_moves用最顶行判断列是否已满复杂度 O(COLS)比每列从下往上扫一遍更省。drop_piece从底往上找空位返回行号是为了后续做胜负判断时能只检查落子点周围而不是全盘扫描。参数上ROWS和COLS一旦确定就不要在搜索中动态改否则缓存和剪枝都会失效。面试里如果被问“能不能用一维数组”答可以用index r * COLS c映射但重力模拟时行号计算要额外小心容易翻车。2.2 极小化极大搜索与 Alpha-Beta 剪枝深度和顺序决定成败这是整场面试的核心。面试官会先让你写一个不带剪枝的 minimax再让你加 Alpha-Beta然后追问剪枝效率取决于什么。标准答法是Alpha-Beta 的剪枝量高度依赖走法排序好的排序能让有效分支因子从 b 降到 sqrt(b)差的排序退化成和 minimax 一样。def minimax(board, depth, maximizing, ai_piece, human_piece): 不带剪枝的极小化极大depth 为剩余搜索深度 if depth 0 or is_terminal(board): return evaluate(board, ai_piece, human_piece) if maximizing: best -float(inf) for col in get_legal_moves(board): r drop_piece(board, col, ai_piece) score minimax(board, depth - 1, False, ai_piece, human_piece) board[r][col] EMPTY # 回溯 best max(best, score) return best else: best float(inf) for col in get_legal_moves(board): r drop_piece(board, col, human_piece) score minimax(board, depth - 1, True, ai_piece, human_piece) board[r][col] EMPTY best min(best, score) return best这段代码的讲解重点不在递归本身而在三个容易被追问的点。第一is_terminal要在落子后立刻判断胜负不能等到 depth 为 0否则会漏掉“提前获胜”的分支。第二回溯时用board[r][col] EMPTY而不是重新拷贝棋盘拷贝在深层搜索里是性能杀手。第三evaluate的符号方向要和 maximizing 一致AI 为正、人类为负搞反了搜索会主动送棋。面试官常在这里设陷阱让你把max和min对调看你能不能发现评估函数的方向问题。加 Alpha-Beta 时核心是把当前已知的最好值传下去一旦发现某个分支不可能更好就剪掉。这类题通常这么答先讲 alpha 是 maximizer 的下界、beta 是 minimizer 的上界再讲剪枝条件alpha beta时直接返回。面试官最想听的是你能不能说清“剪枝不改变结果只减少搜索量”以及“走法排序为什么影响剪枝率”。2.3 评估函数设计权重不是玄学是可解释的取舍评估函数是面试里最能拉开差距的地方。很多人写一个“连三给 100 分、连二给 10 分”就交差被追问“为什么是 100 不是 80”就答不上来。这类题通常这么答先列特征再给权重最后说明权重之间的相对关系比绝对值更重要。特征含义建议权重方向常见失分点连四直接获胜极大正值忘记在搜索中提前返回活三两端可延伸的三连高正/负值只数数量不看两端是否开放冲三一端被堵的三连中等值和活三给同权重活二两端开放的二连低正值忽略对手的活二中心列控制中间列落子机会多小幅加分权重过大导致乱下中间评估函数最容易翻车的地方是“只评估自己、不评估对手”。正确做法是对 AI 和人类分别算分再相减或者用同一套特征对双方取差值。面试官如果问“你的 AI 会不会只顾进攻”就是在试探你有没有做对手威胁的负向评估。另一个高频追问是“评估函数和搜索深度的关系”深度浅时评估函数要更准深度深时可以适当简化因为搜索本身会弥补评估的粗糙。这个取舍能讲清楚基本就过了评估这一关。3. 高频真题演练五道面试官最爱追问的题3.1 真题一为什么重力四子棋比普通四子棋更适合考搜索这道题考的是你对问题特性的理解。标准答法分三层第一重力约束让合法走法从“任意空格”变成“每列一个落点”分支因子从最多 42 降到 7搜索树更窄适合在有限时间内展示搜索深度第二落子后胜负判断只需检查落子点四个方向实现干净面试官容易验证你的代码第三重力带来的“列满即不可选”让状态转移有自然终止条件适合考察你对边界情况的处理。面试官最想听的是“分支因子”这个词以及你能不能说清“窄分支让 Alpha-Beta 的收益更明显”。如果你只答“规则简单”会被认为没抓到算法层面的差异。这类题通常这么答先给分支因子对比再给胜负判断的局部性最后落到“适合在面试时间窗内展示完整搜索链路”。3.2 真题二Alpha-Beta 剪枝后结果和 minimax 不一致怎么排查这是血泪经验题面试官想看你的调试思路。答法按顺序来先确认评估函数在剪枝版本里有没有被意外修改再确认 alpha 和 beta 的初始值是不是正负无穷然后检查剪枝条件写的是alpha beta还是alpha beta最后看走法排序有没有在剪枝版本里被改掉。def alphabeta(board, depth, alpha, beta, maximizing, ai_piece, human_piece): 带剪枝的搜索alpha 为下界beta 为上界 if depth 0 or is_terminal(board): return evaluate(board, ai_piece, human_piece) moves order_moves(board, get_legal_moves(board)) # 排序影响剪枝率 if maximizing: value -float(inf) for col in moves: r drop_piece(board, col, ai_piece) value max(value, alphabeta(board, depth - 1, alpha, beta, False, ai_piece, human_piece)) board[r][col] EMPTY alpha max(alpha, value) if alpha beta: break # 剪枝minimizer 不会选这个分支 return value else: value float(inf) for col in moves: r drop_piece(board, col, human_piece) value min(value, alphabeta(board, depth - 1, alpha, beta, True, ai_piece, human_piece)) board[r][col] EMPTY beta min(beta, value) if alpha beta: break return value讲解时重点说order_moves它把“能立刻获胜的走法”和“能堵对手获胜的走法”排前面剪枝率会明显提升。参数上alpha 和 beta 在递归中要按当前层的更新值往下传不能传原始值。如果结果不一致八成是某一层忘了更新 alpha 或 beta或者回溯时棋盘没恢复干净。面试官听到你按“评估函数→初始值→剪枝条件→排序”的顺序排查就知道你真正调过这类代码。3.3 真题三搜索深度加到 8 就超时怎么优化这道题考工程取舍。答法给三个方向按代价从低到高第一加走法排序让 Alpha-Beta 在浅层就剪掉大量分支这是性价比最高的第二加置换表把已经算过的棋盘状态和分数缓存起来重力四子棋的重复状态不少命中率可观第三降评估函数复杂度把全盘扫描改成只扫落子点周围。面试官最想听的是你能不能说清“深度每加一层节点数大约乘分支因子剪枝后乘 sqrt(分支因子)”以及“置换表的键怎么设计”。键可以用棋盘序列化后的字符串或哈希但要注意深度不同分数不可直接复用缓存里要带深度。这类题通常这么答先给优化顺序再给预期收益最后说“如果还超时就把深度做成迭代加深先浅层出结果再逐层加”。3.4 真题四评估函数里活三和冲三权重怎么定这是评估函数的细节题。答法先定义活三是两端都空的连续三子冲三是一端被堵的三子。活三的威胁远大于冲三因为活三下一步可以变成活四冲三只能变冲四。权重上活三给高分、冲三给中分具体倍数关系建议活三是冲三的 3 到 5 倍而不是简单相加。面试官追问“为什么不是 2 倍”时答“因为活三的后续扩展路径更多对手必须同时堵两端防守成本更高”。这类题通常这么答先给定义再给相对倍数最后说“绝对值不重要重要的是活三、冲三、活二之间的序关系不能乱”。如果你把冲三权重给得比活三还高AI 会去追没威胁的棋这是常见翻车点。3.5 真题五怎么判断你的 AI 是真的强还是评估函数碰巧这道题考实验设计。答法固定先手后手各跑若干局统计胜率再做消融实验把评估函数换成随机或全零看胜率掉多少最后做深度对比深度 4 和深度 6 各跑一批看提升是否来自搜索而非评估。面试官最想听的是“消融”这个词以及你有没有意识到“赢了人类不代表搜索正确”。这类题通常这么答先给评测协议再给消融思路最后说“如果深度增加胜率不升说明评估函数在误导搜索”。能讲到这一层说明你不是只会调参而是有验证意识。4. 易错点五个让面试官皱眉的失分动作4.1 错把“列满”当成非法输入直接抛异常错在哪落子函数遇到满列时抛异常或返回错误码但搜索里合法走法生成已经跳过了满列正常流程不该触发。为什么错这说明考生没把“走法生成”和“落子执行”的职责分清楚搜索树里混入了非法分支。怎么答对合法走法生成负责过滤满列落子函数只处理已确认合法的列满列判断作为防御性检查返回 -1 即可不抛异常。4.2 评估函数只算 AI 不算人类错在哪AI 连三给 100 分人类连三给 0 分结果 AI 只顾自己进攻被对手先成四。为什么错博弈评估的本质是相对优势不是绝对得分。怎么答对对双方用同一套特征分别算分再相减或者给人类威胁加负向权重确保“对手的活三”和“自己的活三”在分数上对称。4.3 Alpha-Beta 里忘了回溯棋盘错在哪递归返回后没有把落子位置恢复为空导致后续分支在脏棋盘上搜索。为什么错这类 bug 不会报错只会让 AI 下出莫名其妙的自杀棋排查成本极高。怎么答对落子后立刻记录行号递归返回后马上恢复恢复语句紧跟在递归调用下一行不要隔开。4.4 胜负判断只查落子点的一个方向错在哪只检查水平方向或只检查一个斜向漏掉垂直和另一条斜线。为什么错重力四子棋的垂直四连很常见漏查垂直会直接判错胜负。怎么答对落子后检查四个方向水平、垂直、主对角线、副对角线每个方向从落子点向两侧延伸计数达到四即胜。4.5 搜索深度和评估函数不匹配错在哪深度只有 2 却用很粗糙的评估函数或者深度 8 还用全盘扫描的复杂评估。为什么错浅层搜索靠评估补深层搜索靠搜索补两者错配会让性能或棋力一边倒。怎么答对先定深度再定评估复杂度浅层用细评估、深层用粗评估并在实验里做深度与评估的交叉对比。5. 答题节奏把搜索链路讲成一条线面试里最稳的节奏是“先框架后细节先正确后优化”。拿到题先花三十秒说清整体链路状态表示→合法走法→搜索→评估→胜负判断让面试官知道你有全局观。然后按链路逐段展开每段先给最简正确版本再给优化点。比如搜索先写不带剪枝的 minimax确认逻辑对了再加 Alpha-Beta这样即使时间不够你也留下了一个可运行的正确版本。具体到白板写代码建议按这个顺序落笔先写get_legal_moves和drop_piece再写is_terminal和胜负判断然后写evaluate的骨架最后写搜索。评估函数的权重可以先写占位值等搜索跑通再调。面试官如果中途打断问“这里为什么这么写”不要慌先答“这是为了保证正确性优化放在后面”再把优化思路补上。阶段要讲的话要避免的话开场整体链路五步先正确后优化直接贴一大段搜索代码状态表示二维数组、重力从底往上找空位只说“用数组存”不说重力怎么模拟搜索先 minimax 再 Alpha-Beta剪枝不改结果一上来就写剪枝不讲为什么评估双方对称、相对权重、深度匹配只给数字不说倍数关系收尾消融实验验证、迭代加深兜底“调参调出来的”押题方向上重力四子棋 AI 的变体题大概率围绕三个点转换成更大棋盘怎么改参数、加入禁手或特殊规则怎么调整合法走法、多线程搜索怎么切分。准备时把“分支因子、剪枝率、评估对称性”这三个词挂在嘴边遇到变体先往这三个维度上靠基本不会跑偏。从那以后我每次准备这类博弈题都强制自己先在白纸上画一遍“状态→走法→搜索→评估→胜负”的链路图再动键盘。图没画完就写代码十有八九会在回溯或评估方向上翻车。希望帮到你。本文还有配套的精品资源点击获取