BoggleSolver 普林斯顿 算法第四版

BoggleSolver 普林斯顿 算法第四版



一、 引言

作业链接https://coursera.cs.princeton.edu/algs4/assignments/boggle/specification.php

作业答疑https://coursera.cs.princeton.edu/algs4/assignments/boggle/faq.php

1. Boggle

Boggle猜谜游戏。Boggle是由Allan Turoff设计并由孩之宝发行的文字游戏。它包括一块由16个立方体骰子组成的板,每个骰子的6个面上各印一个字母。在游戏开始时,摇动16个骰子并随机分配到一个4乘4的托盘中,只有骰子的顶面可见。
合法的单词满足以下规则:

  • 一个有效单词必须由一系列相邻的骰子组成。如果两个骰子是水平、垂直或对角相邻的,则两个骰子是相邻的。
  • 一个有效单词最多只能出现一次。
  • 有效单词必须至少包含3个字母。
  • 有效单词必须在字典中(通常不包含专有名词)

以下是一些合法的情况:
在这里插入图片描述

2. 计分

使用此表,根据有效单词的长度对其进行评分
在这里插入图片描述

3. Qu特殊情况

在英语中,字母Q后面几乎总是紧跟着字母U。因此,一个模具的侧面打印的是两个字母的序列Qu,而不是Q(在形成单词时,这两个字母的序列必须一起使用)。得分时,Qu算作两个字母;例如,单词队列按5个字母的单词计分,即使它是由4个骰子组成的。

4. 任务要求

本次lab的任务就是编写一个Boggle解算器,使用给定的字典在给定的Boggle板中查找所有有效单词


二、分析

1.推荐的实现步骤

  1. 熟悉BoogleBoard.java数据结构,这个数据结构可以直接使用,不用更改。
  2. 使用标准数据结构来表示字典,如SET, TreeSet, HashSet.
  3. 创建数据结构BoggleSolver,编写一个基于深度优先搜索的方法,以枚举可由以下相邻骰子序列组成的所有字符串。也就是说,枚举Boggle图中的所有简单路径(但不需要显式形成图)。现在,忽略特殊的两个字母序列 Qu 。
  4. 现在,实现以下关键回溯优化:当当前路径对应的字符串不是字典中任何单词的前缀时,无需进一步扩展路径。为此,需要为支持前缀查询操作的词典创建数据结构:给定前缀,词典中是否有以该前缀开头的单词?
  5. 处理特殊的两个字母序列 Qu 。

二、代码分析与实现

1.针对dice,创建图(非显示)

创建无向图的邻接矩阵,但不使用官方包里面 graph 数据结构,而是使用 bag 直接创建

在这里插入图片描述

代码实现:

    private void CreatAdj(BoggleBoard board){
        this.board=board;
        cols= this.board.cols();
        rows= this.board.rows();
        adj= new Bag[cols*rows];//建立bag,长度为dice个数

        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                int v=i*cols+j;//当前dice的索引
                adj[v] = new Bag<Integer>();//为每个dice建立一个bag
                //八个方向
                if(isExist(i-1,j-1))  adj[v].add((i-1)*cols+(j-1));//left_top
                if(isExist(i-1,j))  adj[v].add((i-1)*cols+(j));//top
                if(isExist(i-1,j+1))  adj[v].add((i-1)*cols+(j+1));//right_top
                if(isExist(i,j+1))  adj[v].add((i)*cols+(j+1));//right
                if(isExist(i+1,j+1))  adj[v].add((i+1)*cols+(j+1));//right_bottom
                if(isExist(i+1,j))  adj[v].add((i+1)*cols+(j));//bottom
                if(isExist(i+1,j-1))  adj[v].add((i+1)*cols+(j-1));//left_bottom
                if(isExist(i,j-1))  adj[v].add((i)*cols+(j-1));//left
            }
        }

//        for(int i=0;i< adj.length;i++){
//            int r=i/cols;
//            int c=i%cols;
//            StdOut.printf("(%d,%d): ",r,c);
//            for(Integer s:adj[i]){
//                StdOut.printf(" (%d,%d)",s/cols,s%cols);
//            }
//            StdOut.println();
//        }

    }

2. DFS实现(递归实现)

DFS的实现参照了博主的实现方式:首先,DFS的模板大体不变,可参照书上的DFS建立,但具体实现有所不同。考虑如下问题:

假设我们对点0进行DFS,当搜索到的路径为0-1-6时,按照书上例子,我们会对0、1、6都进行了标记,即在以后0,1,6都不会出现再搜索路径中,这与我们要求的不符(如0-5-1就不会在以后的路径中出现)。实际上我们只需要mark当前的搜索路径即可
考虑到DFS的非递归实现采用栈stack,因此可建立一个stack保存当前的搜索路径,入栈标记,出栈则取消标记。
在这里插入图片描述

如下是代码实现:

private void dfs(Integer v, Node x, String str, Stack<Integer> visitingDices){

        if(str.length()>=3&&x!=null){
            if(x.val==1){
                validwords.add(str);
            }
        }


        for(Integer s:adj[v]){
            char c = getLetterOnBoard(s);
            //Queue<String> tmp= (Queue<String>) dic.keysWithPrefix(str+c);
            if(!marked[s] &&x!=null){
                if(x.next[c - 'A'] != null){
                    visitingDices.push(s);
                    marked[s]=true;

                    if(c=='Q'){
                        dfs(s,x.next['Q'-'A'].next['U'-'A'],str+"QU",visitingDices);
                    }else{
                        dfs(s,x.next[c-'A'],str+c,visitingDices);
                    }


                    int index = visitingDices.pop();
                    marked[index]=false;
                }
            }

        }

    }

3.前缀搜索

参考书上的算法5.4 基于单词查找树的符号表
实现 get 和 put 的 API。并优化Node的大小为26,即字母表长度。

    private static class Node{
        private int val;
        private final Node[] next = new Node[26];
    }

    private int get(String key){
        Node x= get(root, key, 0);
        if(x==null) return 0;
        return x.val;
    }

    private Node get(Node x,String key,int d){
        if(x==null) return null;
        if(d==key.length()) return x;
        int c = key.charAt(d)-'A';
        return get(x.next[c], key, d+1);
    }

    private void put(String key,int val){
        root=put(root, key, val, 0);
    }

    private Node put(Node x, String key,int val,int d){
        if(x == null) x = new Node();
        if(d == key.length()){
            x.val = 1;
            return x;
        }
        int c=key.charAt(d)-'A';
        x.next[c] = put(x.next[c],key,val,d+1);
        return x;
    }

4.详细代码

BoggleSolver


总结

本次作业难度尚可,也参考了较多博客,DFS实现和前缀搜索的实现较为困难,但好在书上的例子作为参考,因此也能较为顺利的完成。最终代码的得分为95/100
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值