猜单词(Hangman)是一款经典的文字益智游戏。玩家需要在有限的猜测次数内,根据已揭示的字母位置推断出隐藏的单词。本文用Java实现一个智能猜单词AI,讲解如何利用信息熵理论进行最优字母选择,结合英语字母频率统计提升猜测效率,包含完整代码与复杂度分析。
游戏机制与问题建模
在标准猜单词游戏中,系统随机选择一个目标单词,玩家每次猜一个字母。若该字母存在于单词中,则揭示所有出现位置;否则扣除一次生命。玩家需在生命耗尽前猜出完整单词。
将问题抽象为信息论模型:每次猜测的本质是从26个字母中选择一个,使得该猜测能够最大程度地缩小可能的单词集合。信息熵正是衡量”不确定性减少程度”的数学工具。
信息熵理论
香农信息熵定义为:
$$H(X) = -\sum_{i} p(x_i) \log_2 p(x_i)$$
在猜单词场景中,将候选单词集合 $S$ 按照字母 $c$ 的出现模式划分为若干子集。例如,对于5字母单词和字母’a’,可能的模式包括:”不出现在任何位置”、”出现在第1位”、”出现在第2位”等。每种模式对应一个子集 $S_i$,其概率 $p_i = |S_i| / |S|$。
字母 $c$ 的期望信息增益(即条件熵减少量)为:
$$Gain(c) = H(S) – \sum_{i} p_i \cdot H(S_i)$$
选择使 $Gain(c)$ 最大的字母作为下一次猜测。
工程实现架构
整体采用三层架构:
– 词典层:加载并维护候选单词集合,支持按长度和已知模式过滤
– 策略层:实现多种猜测策略(信息熵、字母频率、随机等)
– 游戏层:处理游戏状态流转、胜负判定与人机交互
核心算法实现
import java.util.*;
import java.util.stream.*;
/**
* 猜单词智能AI - 基于信息熵的最优字母选择
*/
public class HangmanSolver {
private List<String> dictionary; // 完整词典
private List<String> candidates; // 当前候选集合
private Set<Character> guessed; // 已猜测字母
private String pattern; // 当前已揭示模式,如 "A__LE"
private int wordLength;
public HangmanSolver(List<String> dict, int length) {
this.dictionary = new ArrayList<>(dict);
this.wordLength = length;
this.guessed = new HashSet<>();
// 筛选指定长度的单词
this.candidates = dictionary.stream()
.filter(w -> w.length() == length)
.filter(w -> w.chars().allMatch(Character::isLetter))
.map(String::toUpperCase)
.collect(Collectors.toList());
this.pattern = "_".repeat(length);
}
/**
* 信息熵策略:选择期望信息增益最大的字母
*/
public char entropyBasedGuess() {
if (candidates.isEmpty()) return randomGuess();
double bestGain = -1;
char bestChar = '?';
for (char c = 'A'; c <= 'Z'; c++) {
if (guessed.contains(c)) continue;
// 按字母c的出现模式对候选集分组
Map<String, List<String>> groups = new HashMap<>();
for (String word : candidates) {
String mask = getMask(word, c);
groups.computeIfAbsent(mask, k -> new ArrayList<>()).add(word);
}
// 计算期望信息增益
double expectedEntropy = 0;
int total = candidates.size();
for (List<String> group : groups.values()) {
double p = (double) group.size() / total;
expectedEntropy -= p * Math.log(p) / Math.log(2);
}
// 信息增益 = 当前熵 - 期望条件熵
double currentEntropy = Math.log(total) / Math.log(2);
double gain = currentEntropy - expectedEntropy;
if (gain > bestGain) {
bestGain = gain;
bestChar = c;
}
}
return bestChar;
}
/**
* 频率策略:选择在候选集中出现频率最高的字母
*/
public char frequencyBasedGuess() {
int[] freq = new int[26];
for (String word : candidates) {
Set<Character> unique = new HashSet<>();
for (char c : word.toCharArray()) unique.add(c);
for (char c : unique) freq[c - 'A']++;
}
char best = '?';
int maxFreq = -1;
for (char c = 'A'; c <= 'Z'; c++) {
if (!guessed.contains(c) && freq[c - 'A'] > maxFreq) {
maxFreq = freq[c - 'A'];
best = c;
}
}
return best;
}
/**
* 获取单词在目标字母下的位置掩码
* 例如 getMask("APPLE", 'P') -> "_PP__"
*/
private String getMask(String word, char target) {
StringBuilder sb = new StringBuilder();
for (char c : word.toCharArray()) {
sb.append(c == target ? target : '_');
}
return sb.toString();
}
/**
* 更新游戏状态:根据猜测结果过滤候选集
* @param letter 猜测的字母
* @param positions 该字母出现的所有位置(空表示未出现)
*/
public void updateState(char letter, List<Integer> positions) {
guessed.add(letter);
if (positions.isEmpty()) {
// 字母不存在:过滤掉包含该字母的候选
candidates.removeIf(w -> w.indexOf(letter) >= 0);
} else {
// 字母存在:只保留在对应位置出现该字母的候选
candidates.removeIf(w -> {
for (int pos : positions) {
if (w.charAt(pos) != letter) return true;
}
// 确保该字母只出现在这些位置
for (int i = 0; i < wordLength; i++) {
if (!positions.contains(i) && w.charAt(i) == letter) return true;
}
return false;
});
// 更新已揭示模式
char[] pat = pattern.toCharArray();
for (int pos : positions) pat[pos] = letter;
pattern = new String(pat);
}
}
public boolean isSolved() {
return !pattern.contains("_");
}
public String getPattern() { return pattern; }
public int getCandidateCount() { return candidates.size(); }
// 随机猜测兜底
private char randomGuess() {
List<Character> available = new ArrayList<>();
for (char c = 'A'; c <= 'Z'; c++) if (!guessed.contains(c)) available.add(c);
return available.get(new Random().nextInt(available.size()));
}
}
/**
* 游戏主控与交互
*/
public class HangmanGame {
private static final int MAX_LIVES = 6;
private String target;
private int lives;
private HangmanSolver solver;
public HangmanGame(String word, List<String> dictionary) {
this.target = word.toUpperCase();
this.lives = MAX_LIVES;
this.solver = new HangmanSolver(dictionary, word.length());
}
/**
* AI自动玩一局
*/
public boolean playAuto() {
System.out.println("目标单词长度: " + target.length());
while (lives > 0 && !solver.isSolved()) {
char guess = solver.entropyBasedGuess();
List<Integer> positions = new ArrayList<>();
for (int i = 0; i < target.length(); i++) {
if (target.charAt(i) == guess) positions.add(i);
}
boolean hit = !positions.isEmpty();
solver.updateState(guess, positions);
System.out.printf("猜测: %c | %s | 候选集: %d | 模式: %s%n",
guess, hit ? "命中" : "未命中(-1生命)",
solver.getCandidateCount(), solver.getPattern());
if (!hit) lives--;
}
System.out.println(solver.isSolved() ? "AI获胜!" : "AI失败,目标: " + target);
return solver.isSolved();
}
public static void main(String[] args) {
// 示例词典(实际应加载更大词典)
List<String> dict = Arrays.asList(
"APPLE", "APPLY", "ABOUT", "ABOVE", "ALERT", "ALBUM", "ALIVE",
"BRAVE", "BRAIN", "BREAD", "BREAK", "BRING", "BROWN", "BUILD",
"CANDY", "CARRY", "CATCH", "CAUSE", "CHAIN", "CHAIR", "CHART",
"DANCE", "DAILY", "DEATH", "DEPTH", "DOUBT", "DREAM", "DRIVE",
"EARTH", "EARLY", "EIGHT", "ENJOY", "ENTER", "EQUAL", "EVERY",
"FAITH", "FAULT", "FIELD", "FIGHT", "FINAL", "FIRST", "FOCUS",
"GLASS", "GLOBAL", "GRAND", "GRANT", "GRASS", "GREAT", "GREEN",
"HEART", "HEAVY", "HORSE", "HOTEL", "HOUSE", "HUMAN", "HAPPY",
"IMAGE", "INDEX", "INNER", "INPUT", "ISSUE", "IDEAL", "IRONY",
"JUDGE", "JOINT", "JUICE", "JUMPY"
);
HangmanGame game = new HangmanGame("APPLE", dict);
game.playAuto();
}
}
算法复杂度分析
信息熵计算:对于候选集大小 $n$、单词长度 $m$,每次猜测需要遍历26个字母,对每个字母遍历 $n$ 个候选词并计算掩码。时间复杂度 $O(26 \cdot n \cdot m) = O(n \cdot m)$。
候选集过滤:每次更新需要遍历候选集并逐字符比对,复杂度 $O(n \cdot m)$。
空间复杂度:存储候选集需要 $O(n \cdot m)$ 空间。
策略对比与优化
| 策略 | 平均猜测次数 | 计算开销 | 适用场景 |
|---|---|---|---|
| 信息熵 | 最少 | 高 | 追求最优解 |
| 字母频率 | 较少 | 低 | 实时性要求高 |
| 随机 | 最多 | 最低 | 基准对比 |
实际工程中可采用混合策略:前期用信息熵快速缩小范围,候选集小于阈值后切换至频率策略以降低计算开销。
扩展方向
- N-gram语言模型:利用字母组合条件概率进一步优化候选排序
- 多单词并行猜测:同时维护多个可能目标的后验概率分布
- 自适应词典:根据用户历史行为动态调整先验概率
总结
本文通过信息熵理论为猜单词游戏构建了最优决策框架。核心思想是将每次猜测转化为对候选集合的划分问题,选择使期望熵减最大的字母。Java实现中通过掩码分组和集合过滤高效维护游戏状态,为文字类益智游戏的AI设计提供了通用范式。