编程作业四

作业链接:Boggle & Checklist

我的代码:BoggleSolver.java

问题简介

Boggle 是一个文字游戏,有 16 个每面都有字母的骰子,开始随机将它们放在 4 * 4 的板子上,如图:

图片来自:wiki

游戏双方就在上面找有效的单词,单词越长分数越高,有效的单词具体指:

  • 有效单词相邻字符的骰子也相邻,上下左右,对角线也行。
  • 有效单词不能重复使用某个骰子。
  • 有效单词的长度至少为 3。
  • 有效单词要能在字典中找到,专有名词不算。

举例:

左边的单词 PINES 是有效的,右边的 DATES 是无效的(显然不相邻)。

不同长度的单词计分如下:

word length points
0 - 2 0
3 - 4 1
5 2
6 3
7 5
8+ 11

最后,因为英语单词中字母 Q 后面几乎总是会跟着 U,所以直接在骰子上写了 QU,计分的时候按两个字母算。

任务摘要

Your task. Your challenge is to write a Boggle solver that finds all valid words in a given Boggle board, using a given dictionary. Implement an immutable data type BoggleSolver with the following API:

public class BoggleSolver
{
// Initializes the data structure using the given array of strings as the dictionary.
// (You can assume each word in the dictionary contains only the uppercase letters A through Z.)
public BoggleSolver(String[] dictionary) // Returns the set of all valid words in the given Boggle board, as an Iterable.
public Iterable<String> getAllValidWords(BoggleBoard board) // Returns the score of the given word if it is in the dictionary, zero otherwise.
// (You can assume the word contains only the uppercase letters A through Z.)
public int scoreOf(String word)
}

详细要求参见:Boggle

问题分析

照着 Checklist 里建议的编程步骤一步步做下来。先熟悉作业为我们提供的数据结构:BoggleBoard.java,这个看下 API 就好,要注意的是板子上的 QU 为方便它直接用 Q 表示,后面我们要自己处理。第二步叫我们选个标准的数据结构来表示输入的字典,像 SET, TreeSet,或是 HashSet,用集合来表示一堆没重复的单词构成的字典,想想也是很合理。但后面觉得是没有必要再特地拿一个集合来表示。

第三步建议我们用 DFS 在 Board 上找出可能的单词组合,这步花了点时间才实现。传统的 DFS 是遍历所有的点,而且只会访问每个点一次。但是这里从不同的路径访问同一个字符显然算两个单词,所以递归返回之前还要把这个位置标记为没有被访问过,允许下次从不同的路再访问它。再者,路径上的字符要连起来才能构成单词,于是递归的 DFS 我还加了个 StringBuilder 类型的参数 pre,每次 new 一下传到下一层再接上这个位置的字符。

第四步是很重要的一步,像第三步那样找出相邻字符所有可能组成的单词,我在四乘四的板子上都跑到堆栈溢出。其实,发现字典里没有以当前组成的 pre 为前缀的单词时,这条 DFS 就不用再递归下去了,有很多 DFS 刚开始就会结束,而且基本不会有跑到底的 DFS。建议我们再实现一个数据结构,用来查询字典中是否有以 pre 为前缀的单词。

那第二步集合只是为了方便判断某个单词是否在字典中吗,那完全可以和第四步合起来吧我觉得,像 TrieSet 里就有这两个方法。但是课程里实现的 TrieSET.java 支持的是拓展 ASCII 的 256-way Trie,这里只会有 A 到 Z 共 26 个单词,感觉会浪费很多空间。于是,一开始调用了课程里有的 TST.java(三向单词查找树实现的符号表),以单词(字符串)为键,值随便。最后让我们解决特殊的 "QU" 情形,问题不大,字符是 Q,给 pre 拼接上 QU 就好。

又一番调试,本地测试通过,先交一波上去评测。拿到了 92 分,似乎开了个头,但实际上前面就做挺久了。。。具体是有四个时间测试没有通过,是参考程序的两百多倍。

Test 2: timing getAllValidWords() for 5.0 seconds using dictionary-yawl.txt
(must be <= 2x reference solution)
- reference solution calls per second: 8870.50
- student solution calls per second: 42.90
- reference / student ratio: 206.79 => passed student <= 10000x reference
=> FAILED student <= 25x reference
=> FAILED student <= 10x reference
=> FAILED student <= 5x reference
=> FAILED student <= 2x reference

那没办法,考虑 Checklist 里建议的可能的优化部分:

Possible Optimizations

You will likely need to optimize some aspects of your program to pass all of the performance points (which are, intentionally, more challenging on this assignment). Here are a few ideas:

  1. Make sure that you have implemented the critical backtracking optimization described above. This is, by far, the most important step—several orders of magnitude!

  2. Think about whether you can implement the dictionary in a more efficient manner. Recall that the alphabet consists of only the 26 letters A through Z.

  3. Exploit that fact that when you perform a prefix query operation, it is usually almost identical to the previous prefix query, except that it is one letter longer.

  4. Consider a nonrecursive implementation of the prefix query operation.

  5. Precompute the Boggle graph, i.e., the set of cubes adjacent to each cube. But don't necessarily use a heavyweight Graph object.

第一点已经实现了,本着有现成就尽量不自己写的精神,再试试调用课程里其它的程序:TrieSET.javaTrieST,但本地测试的结果并没有比 TST.java 好,那就只能自己写了。

一开始,还是想着直接用现成的前缀判断方法。先在课程程序的基础上,忙活半天改出了 26-way trie 和 TST with \(R^{2}\) root 这两个版本,但本地测试并没有什么明显的效果,还是一开始的 TST 表现最好,也可能是我改得不好吧。再直接考虑最后一点,预处理一下板子,先算出每个位置的邻居,这样就不用每次 DFS 到了再算一次。效果也一般,而且我一开始用放整数的集合数组(泛型数组)来存,交上去评测编译时会有警告,编译部分还会被扣分。

到了最后的最后,只能自己写单词树了,后来觉得还不如一开始就自己实现好了。然后,当然是参考着课程里的程序来写的,主要目的是优化前缀的查找。因为这边的前缀查找比较特殊,相邻的查找总是只差一个字符,作业就提示我们是否能利用这一现象。在程序里实现单词查找树,前缀的判断就可以融入 DFS。递归传下去的不只是这条路上的 pre,这个 pre 在单词树中的位置节点也可以传下去,只要这个节点一为空,就说明单词树中没有以 pre 为前缀的单词,也就可以跳出这条 DFS。不好说明,可以直接看代码的 DFS 部分:BoggleSolver.java

大概是这么个思想,最终实现自然是又捣鼓了好久。本地测试通过,交上去,拿了 98 分,还差最后一个测试,只要耗时小于参考程序的两倍就好。于是又盯着代码,各种小优化,像测试用随机的 4 * 4 板子,预计算出邻居不用每次都算,像空间还够,用数组存,下标来加快查找等等,但最终还是差那么 0.03。。。

Test 2: timing getAllValidWords() for 5.0 seconds using dictionary-yawl.txt
(must be <= 2x reference solution)
- reference solution calls per second: 9160.06
- student solution calls per second: 4502.85
- reference / student ratio: 2.03 => passed student <= 10000x reference
=> passed student <= 25x reference
=> passed student <= 10x reference
=> passed student <= 5x reference
=> FAILED student <= 2x reference Total: 8/9 tests passed!

去作业论坛寻找新的优化思路,有人说他把板子存成一维的,就不用老是访问作业提供的板子对象查这个位置字符是啥。稍加修改,本地测试感觉良好,貌似会快那么一点点,交上去终于 a 掉了所有点!!

Test 2: timing getAllValidWords() for 5.0 seconds using dictionary-yawl.txt
(must be <= 2x reference solution)
- reference solution calls per second: 6842.46
- student solution calls per second: 4873.98
- reference / student ratio: 1.40 => passed student <= 10000x reference
=> passed student <= 25x reference
=> passed student <= 10x reference
=> passed student <= 5x reference
=> passed student <= 2x reference Total: 9/9 tests passed!

据说(论坛)比参考程序耗时少还会有额外分数,但优化点我都用上了,也不想再优化了,100 分就好啦,哈哈。

测试结果

Programming Assignment 4: Boggle的更多相关文章

  1. 课程一(Neural Networks and Deep Learning),第三周(Shallow neural networks)—— 3.Programming Assignment : Planar data classification with a hidden layer

    Planar data classification with a hidden layer Welcome to the second programming exercise of the dee ...

  2. Algorithms: Design and Analysis, Part 1 - Programming Assignment #1

    自我总结: 1.编程的思维不够,虽然分析有哪些需要的函数,但是不能比较好的汇总整合 2.写代码能力,容易挫败感,经常有bug,很烦心,耐心不够好 题目: In this programming ass ...

  3. Algorithms : Programming Assignment 3: Pattern Recognition

    Programming Assignment 3: Pattern Recognition 1.题目重述 原题目:Programming Assignment 3: Pattern Recogniti ...

  4. Programming Assignment 2: Randomized Queues and Deques

    实现一个泛型的双端队列和随机化队列,用数组和链表的方式实现基本数据结构,主要介绍了泛型和迭代器. Dequeue. 实现一个双端队列,它是栈和队列的升级版,支持首尾两端的插入和删除.Deque的API ...

  5. 课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 2、编程作业常见问题与答案(Programming Assignment FAQ)

    Please note that when you are working on the programming exercise you will find comments that say &q ...

  6. Programming Assignment 5: Kd-Trees

    用2d-tree数据结构实现在2维矩形区域内的高效的range search 和 nearest neighbor search.2d-tree有许多的应用,在天体分类.计算机动画.神经网络加速.数据 ...

  7. Programming Assignment 4: 8 Puzzle

    The Problem. 求解8数码问题.用最少的移动次数能使8数码还原. Best-first search.使用A*算法来解决,我们定义一个Seach Node,它是当前搜索局面的一种状态,记录了 ...

  8. coursera普林斯顿算法课part1里Programming Assignment 2最后的extra challenge

    先附上challenge要求: 博主最近在刷coursera普林斯顿大学算法课part1部分的作业,Programming Assignment2最后的这个extra challenge当初想了一段时 ...

  9. Programming Assignment 2: Deques and Randomized Queues

    编程作业二 作业链接:Deques and Randomized Queues & Checklist 我的代码:Deque.java & RandomizedQueue.java & ...

随机推荐

  1. unity简单动画实现

    1:创建一个Sprite Render (player)的动画对象并添加脚本Player,点击主菜单“Window(视窗)→Animation(动画窗口)”Animation面板(选中需要动画的对象) ...

  2. c#基础学习(0724)之可变参数、ref和out

    params可变参数,无论有几个参数,必须出现在参数列表的最后,可以为可变参数直接传递一个对应类型的数组 #region 可变参数 //1.如果方法有多个参数,可变参数可以作为最后一个参数 //2.可 ...

  3. 使用ms owin 搭建oauth2 server

    http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server 有示例代码 关于token的 ...

  4. ORACLEserver实例DB的概念学习理解与总结【进阶一】

    个人原创,转自请在文章开头显眼位置注明出处:https://www.cnblogs.com/sunshine5683/p/10048824.html 一.以后看一个oracleserver,可以使用如 ...

  5. 如何在PIXI.js里面使用json文件来管理瓦片集(tileset)?

    如何在PIXI.js里面使用json文件来管理瓦片集(tileset)? PIXI建议我们将素材图片汇总成一个瓦片集(tileset),然后用纹理地图集(texture atlas,通常是一个json ...

  6. C#的字节与流

    计算机中文件有很多种,我们知道实际存在计算机中的都是二进制.这里我记录了通过流对文件的读取操作. 一.首先在这里简单涉及下位,字节,字符的概念. 位(bit):可以表示0或1: 字节(byte):由8 ...

  7. mysql 删除某一个数据库下面的所有表,但不删除数据库

    删除某一个数据库下面的所有表,但不删除数据库.该语句经过从concat拼接,最后查询出来的是删除表的语句,然后执行那些查询出来的语句就ok了select concat(‘drop table ‘,ta ...

  8. Elixir木蚂蚁支付服务器验签名方法

    官方范例为java public boolean verify(String sign , String appKey , String orderId) throws UnsupportedEnco ...

  9. MapReduce:Shuffle过程详解

    1.Map任务处理 1.1 读取HDFS中的文件.每一行解析成一个<k,v>.每一个键值对调用一次map函数.                <0,hello you>   & ...

  10. PyQt4(简单布局)

    import sys from PyQt4 import QtCore, QtGui app = QtGui.QApplication(sys.argv) widget = QtGui.QWidget ...