任务链接:http://coursera.cs.princeton.edu/algs4/assignments/boggle.html
这次任务给的需要实现的方法很少,完成本次任务关键在于理清思路,需要实现较多的私有方法。
需要自己设计单词树,将单词树中每个字符节点定义为一个类。
private static class Node // 字典中节点类
{
private boolean isWord; // 到达该节点是否为单词标志位
private Node[] next = new Node[R]; // 该节点的后继节点
}
该单词节点类有一个标志位,表示从头节点到该节点所构成字符序列为一个单词。next数组表示该字符之后接下来可能出现的26个字符节点类。
另外设计一个存储相邻节点下标的类,用于存储该节点在面板中相邻的8个(最多)方向的字符的下标,并用n标记真实数组的长度
private static class Adjacent // 游戏面板相邻节点类
{
private int n = 0; // 相邻节点数组长度
private int[] neighbor = new int[8]; // 相邻节点
}
设计步骤:
首先,我们需要为给定的单词词集设计成单词树。
然后对面板的字符采用DFS方法进行组成字符序列查找单词树。当查到单词树的叶子节点时就停止本次查找。和之前的DFS不同的时,在对mark数组进行处理的时候,在退出递归查找之前需将给位置mark设置为false,以便别的单词序列能包含该字符。
其中为了节省时间,需提前计算好每个位置上的字符相邻字符的下标。即利用上面的Adjacent类。
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.SET;
import edu.princeton.cs.algs4.StdOut;
public class BoggleSolver
{
private static final int R = 26; // A-Z
private boolean[][] marked; // 游戏面板遍历标志位
private char[][] boardChar; // 游戏面板存放字符
private Adjacent[] adjacents; // 相邻节点数组
private int row; // 行
private int col; // 列
private Node root; // 字典根节点
private static class Node // 字典中节点类
{
private boolean isWord; // 到达该节点是否为单词标志位
private Node[] next = new Node[R]; // 该节点的后继节点
}
private static class Adjacent // 游戏面板相邻节点类
{
private int n = 0; // 相邻节点数组长度
private int[] neighbor = new int[8]; // 相邻节点
}
// 使用给定的字符串数组作为字典初始化数据结构。
// (你可以假设字典中的每个单词只包含大写字母A到Z)。
public BoggleSolver(String[] dictionary)
{
if (dictionary == null)
throw new java.lang.IllegalArgumentException("the string[] is null");
// 依次添加进字典中
for (int i = 0; i < dictionary.length; i++)
addToDictionary(dictionary[i]);
}
// 将单词插入字典中
private void addToDictionary(String word)
{
if (word == null)
throw new java.lang.IllegalArgumentException("the string is null");
root = add(root, word, 0);
}
// 对单词的字符进行操作
private Node add(Node x, String word, int d)
{
if (x == null)
x = new Node();
if (d == word.length()) // 到达单词结尾时,在对应的字符节点处设置为是单词标志
x.isWord = true;
else {
char c = word.charAt(d);
x.next[c - 'A'] = add(x.next[c - 'A'], word, d + 1);
}
return x;
}
// 判断字典中是否有该单词
private boolean contains(String word)
{
if (word == null)
throw new java.lang.IllegalArgumentException("the word is null");
Node x = get(root, word, 0);
if (x == null)
return false;
else
return x.isWord;
}
// 以递归方式查找单词
// 当对应位置无字符时返回null,否则返回该字符节点
private Node get(Node node, String word, int d)
{
if (node == null) // 字典中该位置没有字符存储
return null;
else {
if (d < word.length()) { // 递归查找下一节点
char c = word.charAt(d);
return get(node.next[c - 'A'], word, d + 1);
}
else
return node;
}
}
// 返回给定的BogGrand板中所有有效单词的集合,作为一个迭代。
public Iterable<String> getAllValidWords(BoggleBoard board)
{
if (board == null)
throw new java.lang.IllegalArgumentException("the board is null");
// 减少内存
if (row != board.rows() || col != board.cols())
{
row = board.rows(); // 行
col = board.cols(); // 列
marked = new boolean[row][col]; // 面板标志位
boardChar = new char[row][col]; // 面板字符位
computeAdj(); // 计算相邻位置的下标
}
for (int i = 0; i < row; i++) // 行
for (int j = 0; j < col; j++) // 列
boardChar[i][j] = board.getLetter(i, j); // 拷贝字母
SET<String> words = DFS();
return words;
}
// 计算相邻位置的下标
private void computeAdj()
{
adjacents = new Adjacent[row * col]; // 相邻节点数组
for (int i = 0; i < row; i++) { // 行
for (int j = 0; j < col; j++) { // 列
int index = i * col + j;
adjacents[index] = new Adjacent(); // adj[index].neineighbor[k]表示 临近节点的下标
if (i > 0) // 上
{
// 左上
if (j > 0)
adjacents[index].neighbor[adjacents[index].n++] = (i - 1) * col + j - 1;
// 正上
adjacents[index].neighbor[adjacents[index].n++] = (i - 1) * col + j;
// 右上
if (j < col -1)
adjacents[index].neighbor[adjacents[index].n++] = (i - 1) * col + j + 1;
}
// 右
if (j < col -1)
adjacents[index].neighbor[adjacents[index].n++] = i * col + j + 1;
if (i < row -1) // 下
{
// 右下
if (j < col -1) {
adjacents[index].neighbor[adjacents[index].n++] = (i + 1) * col + j + 1;
}
// 正下
adjacents[index].neighbor[adjacents[index].n++] = (i + 1) * col + j;
// 左下
if (j > 0)
adjacents[index].neighbor[adjacents[index].n++] = (i + 1) * col + j - 1;
}
// 左
if (j > 0)
adjacents[index].neighbor[adjacents[index].n++] = i * col + j - 1;
}
}
}
// 深度优先遍历搜索
private SET<String> DFS()
{
SET<String> words = new SET<>(); // 用于存放找到的单词
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
DFS(i, j, new StringBuilder(), words, root);
return words;
}
// 深度优先搜索面板
private void DFS(int indexi, int indexj, StringBuilder pre, SET<String> words, Node node)
{
char c = boardChar[indexi][indexj];
Node next = node.next[c - 'A'];
if (c == 'Q' && next != null)
next = next.next['U' - 'A'];
if (next == null)
return;
if (c == 'Q') // QU问题
pre.append("QU");
else
pre.append(c);
String string = pre.toString();
if (pre.length() > 2 && next.isWord) // 是单词
words.add(string);
marked[indexi][indexj] = true;
for (int i = 0; i < adjacents[indexi * col + indexj].n; i++) {
int indexk = adjacents[indexi * col + indexj].neighbor[i]; //相邻节点的下标
int indexrow = indexk / col; // 该下标的节点所在行
int indexcol = indexk % col; // 该下标节点所在的列
if (!marked[indexrow][indexcol])
DFS(indexrow, indexcol, new StringBuilder(pre), words, next);
}
marked[indexi][indexj] = false; // 以便别的单词能继续查找
}
// 如果它在字典中返回给定单词的分数,否则为零。
// (你可以假设这个词只包含大写字母A到Z)。
public int scoreOf(String word)
{
if (word == null)
throw new java.lang.IllegalArgumentException("the word is null");
if (!contains(word))
return 0;
if (word.length() <= 2)
return 0;
else if (word.length() <= 4)
return 1;
else if (word.length() == 5)
return 2;
else if (word.length() == 6)
return 3;
else if (word.length() == 7)
return 5;
else
return 11;
}
public static void main(String[] args) {
In in = new In(args[0]);
String[] dictionary = in.readAllStrings();
BoggleSolver solver = new BoggleSolver(dictionary);
BoggleBoard board = new BoggleBoard(args[1]);
int score = 0;
for (String word : solver.getAllValidWords(board)) {
StdOut.println(word);
score += solver.scoreOf(word);
}
StdOut.println("Score = " + score);
}
}