编程作业四

作业链接: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. c#socket TCP同步网络通信

    一.socket简介 socket就是套接字,它是引用网络连接的特殊文件描述符,由三个基本要素组成: 1: AddressFamily(网络类型) 2: SocketType(数据传输类型) 3:Pr ...

  2. SQL2008无法连接到(local),该账户当前被锁定,所以Sa用户登陆失败

    1 安装小结 换了电脑,很多软件都得重装,期间报了很多问题,比如说先装vs2008再装sql server2008r2会报一个“存在2008早期版本”,通过查找,百度一系列的坑爹之路后,我还是把vs2 ...

  3. mysql5.0版本下载地址

    http://dev.mysql.com/downloads/mysql/5.0.html Other Downloads: Windows (x86, 32-bit), ZIP Archive 5. ...

  4. GDI+中发生一般性错误的解决办法(转)

    今天在开发.net引用程序中,需要System.Drawing.Image.Save 创建图片,debug的时候程序一切正常,可是发布到IIS后缺提示出现"GDI+中发生一般性错误" ...

  5. 基于asp.net mvc的近乎产品开发培训课程(第三讲)

    演示产品源码下载地址:http://www.jinhusns.com/Products/Download 

  6. Promise异步编程整理

    1.单线程模型 单线程模型指的是,JavaScript 只在一个线程上运行.也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待. 注意,JavaScript 只在一个线 ...

  7. iOS系统库头文件中NS_AVAILABLE相关

    转载: NS_AVAILABLE_IOS(5_0) 这个方法可以在iOS5.0及以后的版本中使用,如果在比5.0更老的版本中调用这个方法,就会引起崩溃. NS_DEPRECATED_IOS(2_0, ...

  8. nc63 树管理型单据的开发

    <?xml version="1.0" encoding="gbk"?><beans xmlns="http://www.sprin ...

  9. windows多线程窗口程序设计

    掌握windows基于消息驱动的窗口应用程序设计的基本方法,掌握窗口程序资源的概念与设计,掌握常用的消息的程序处理方法,掌握文字图形输出相关函数编程.掌握设计的基本方法(选项),掌握时钟消息设计动画程 ...

  10. 给model模型传数组参数

    $res = $this->Company->companyDischarge($this->user_id,array(0=>'c.limit_sum>0',1=> ...