资讯专栏INFORMATION COLUMN

算法(第4版) Chapter 5.2 单词查找树

tigerZH / 1203人阅读

摘要:谢路云单词查找树查找所需要的单词的时间和键的长度成正比查找未命中只需检查若干个单词单词查找树单词查找树基本性质每个链接对应一个字符每个结点可能有一个值有值,说明存在从根结点到这个结点的字符串。它的存在是为了简化查询。

Algorithms Fourth Edition
Written By Robert Sedgewick & Kevin Wayne
Translated By 谢路云
Chapter 5 Section 2 单词查找树

查找所需要的单词的时间和键的长度成正比

查找未命中只需检查若干个单词

单词查找树 单词查找树API

基本性质

每个链接对应一个字符

每个结点可能有一个值

有值,说明存在从根结点到这个结点的字符串。

没有值,说明不存在从根结点到这个结点的字符串。没有对应的键值。它的存在是为了简化查询。

查找

命中: 对应结点有值(注意不单单是指向该对应结点d链接存在,并且该结点一定要有值!)

未命中: 没有值 or 链接为空

结点的表示

每个结点下面有R个链接,一个链接对应一个字符

键隐式地保存在结点里

TrieST 代码
public class TrieST {
    private static int R = 256; // radix
    private Node root; // root of trie

    private static class Node {
        private Object val;
        private Node[] next = new Node[R];
    }

    public Value get(String key) {
        Node x = get(root, key, 0);
        if (x == null)
            return null;
        return (Value) x.val;
    }
    
    // Return value associated with key in the subtrie rooted at x.
    private Node get(Node x, String key, int d) { 
        if (x == null)
            return null;
        if (d == key.length())
            return x;
        char c = key.charAt(d); // Use dth key char to identify subtrie.
        return get(x.next[c], key, d + 1);
    }

    public void put(String key, Value val) {
        root = put(root, key, val, 0);
    }
    
    // Change value associated with key if in subtrie rooted at x.
    private Node put(Node x, String key, Value val, int d) { //神一般的递归思想
        if (x == null)
            x = new Node();
        if (d == key.length()) {
            x.val = val;
            return x;
        }
        char c = key.charAt(d); // Use dth key char to identify subtrie.
        x.next[c] = put(x.next[c], key, val, d + 1); //神来之笔
        return x;
    }

    public Iterable keys() {
        return keysWithPrefix("");
    }

    public Iterable keysWithPrefix(String pre) {
        Queue q = new Queue();
        collect(get(root, pre, 0), pre, q);
        return q;
    }

    private void collect(Node x, String pre, Queue q) {
        if (x == null)
            return;
        if (x.val != null)
            q.enqueue(pre);
        for (char c = 0; c < R; c++) //这个效率简直了。。。烂到爆炸
            collect(x.next[c], pre + c, q);
    }
}

方法keys:返回一个Queue,里面有所有的字符串

方法keysWithPrefix(String pre):返回一个Queue,里面有所有以给定字符串pre开头的所有字符串

方法collect:递归用

通配符匹配

结构差异

不含通配符的结构。keysWithPrefix(); get(); collect();

含通配符的结构。keysWithPrefix(); collect();

为什么结构和之前不一样呢?因为get方法要重写。直接把重写的get方法归并进collect方法里去了。

    public Iterable keysThatMatch(String pat) {
        Queue q = new Queue();
        collect(root, "", pat, q);
        return q;
    }

    public void collect(Node x, String pre, String pat, Queue q) {
        int d = pre.length(); 
        // begin修改于原get方法()和collect方法()
        if (x == null) // get&collect
            return;
        if (d == pat.length() && x.val != null) // 前collect,后get
            q.enqueue(pre);
        if (d == pat.length())// get
            return; 
        // end 修改于原get方法()和collect方法()
        
        char next = pat.charAt(d);
        for (char c = 0; c < R; c++)
            if (next == "." || next == c) //如果next=="." 就要遍历接在当前字符后面的所有字符!!!
                collect(x.next[c], pre + c, pat, q); //还是神一般的递归想法
    }
private Node get(Node x, String key, int d) { 
        if (x == null)
            return null;
        if (d == key.length())
            return x;
        char c = key.charAt(d); // Use dth key char to identify subtrie.
        return get(x.next[c], key, d + 1);
    }
最长前缀
    public String longestPrefixOf(String s) {
        int length = search(root, s, 0, 0);
        return s.substring(0, length);
    }

    //当前搜索的是第d位字符,返回的是具有最长length位前缀的子字符
    private int search(Node x, String s, int d, int length) {
        if (x == null) // 查到单词树的尽头了
            return length;
        if (x.val != null) // 如果存在这个单词,就更新length值
            length = d;
        if (d == s.length()) // 查到字符串的尽头了(一定要先做上一步)
            return length;
        char c = s.charAt(d);
        return search(x.next[c], s, d + 1, length); //递归搜索下一位
    }
删除操作

依旧是神一般的递归思路

如果它的链接不为空

删去这个结点的即可

如果它的所有链接都为空

删去这个结点

检查这个结点的父结点的所有链接是否为空

不为空,结束

为空,删去父结点并检查父结点的父结点是否为空
……循环如此

    public void delete(String key) {
        root = delete(root, key, 0);
    }

    private Node delete(Node x, String key, int d) {
        if (x == null)
            return null;
        if (d == key.length()) //找到了的话就将该结点的值删去
            x.val = null;
        else {
            char c = key.charAt(d);
            x.next[c] = delete(x.next[c], key, d + 1); //依旧是神一般的递归思路
        }
        if (x.val != null) //如果该结点有值,则不管当前结点链接是否为空,该结点都在树里,不能被删去。
            return x;
        for (char c = 0; c < R; c++) //否则就看该结点链接是否为空(即该结点没有值)
            if (x.next[c] != null) //如果当前结点链接不为空,则返回当前结点
                return x; 
        return null; //否则返回为空。(因为是返回上一层递归, 即把自己置为空,也即删除了自己)
    }
复杂度

字母表大小为R,N个随机键组成的单词查找树中

时间:

查找和插入最差时间为:键的长度+1

查找未命中的平均时间为:logRN (查找未命中的时间和键的长度无关

空间

与 R和所有键的字符总数之积 成正比

链接

一棵单词查找树的链接总数为RN到RNw之间,w为键的平均长度

当所有键较短时,链接总数接近RN

当所有键较长时,链接总数接近RNw

缩小R能节省大量空间

三向单词查找树

避免空间消耗

每个结点含有一个字符,三个链接(小于,等于,大于),可能含有一个值

字符是显式保存在每个结点中

TST 代码
public class TST {
    private Node root; // root of trie

    private class Node {
        char c; // character
        Node left, mid, right; // left, middle, and right subtries
        Value val; // value associated with string
    }

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

    private Node get(Node x, String key, int d) {
        if (x == null)
            return null;
        char c = key.charAt(d);
        if (c < x.c)
            return get(x.left, key, d);
        else if (c > x.c)
            return get(x.right, key, d);
        else if (d < key.length() - 1)
            return get(x.mid, key, d + 1);
        else
            return x;
    }

    public void put(String key, Value val) {
        root = put(root, key, val, 0);
    }

    private Node put(Node x, String key, Value val, int d) {
        char c = key.charAt(d);
        if (x == null) {
            x = new Node();
            x.c = c;
        }
        if (c < x.c)
            x.left = put(x.left, key, val, d);
        else if (c > x.c)
            x.right = put(x.right, key, val, d);
        else if (d < key.length() - 1)
            x.mid = put(x.mid, key, val, d + 1);
        else
            x.val = val;
        return x;
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66546.html

相关文章

  • 算法4Chapter 4.3 最小生成

    摘要:算法图示代码复杂度时间初始化优先队列,最坏情况次比较每次操作成本次比较,最多还会多次和次操作,但这些成本相比的增长数量级可忽略不计详见空间 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 谢路云Chapter 4 Section 3 最小生成树 定义 树是特殊的图 图的生...

    asoren 评论0 收藏0
  • 算法4Chapter 4.2 强联通性 Tarjan算法补充

    摘要:在实际的测试中,算法的运行效率也比算法高左右。此外,该算法与求无向图的双连通分量割点桥的算法也有着很深的联系。学习该算法,也有助于深入理解求双连通分量的算法,两者可以类比组合理解。固属于同一连通分支。 参考资料http://blog.csdn.net/acmmmm/a...https://www.byvoid.com/blog/s...http://blog.csdn.net/noth...

    maybe_009 评论0 收藏0
  • 算法4Chapter 1

    摘要:由的位置决定乘以几,依次为,,,,,。递归算法递归算法就是本次结果用另外的参数调用自己。然而这个函数方法本身并没有用,因为方法中若传递参数为基本型如,在方法中对其值的改变并不会在主函数中产生影响。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 谢路云 笔记 二分查找 Bin...

    Jacendfeng 评论0 收藏0
  • 算法4Chapter 4.4 最短路径

    摘要:相关操作就是判断的不等号符号改反,初始值设为负无穷副本的最短路径即为原图的最长路径。方法是同上面一样构造图,同时会添加负权重边,再将所有边取反,然后求最短路径最短路径存在则可行没有负权重环就是可行的调度。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 谢路云Chapter ...

    leap_frog 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<