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

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


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

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

单词查找树

单词查找树API

单词查找树API

基本性质

基本性质

  • 每一个连接对应一个字符code

  • 每一个结点可能有一个值递归

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

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

图片描述

  • 查找字符串

    • 命中: 对应结点有值(注意不仅仅是指向该对应结点d连接存在,而且该结点必定要有值!)get

    • 未命中: 没有值 or 连接为空string

图片描述

  • 结点的表示

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

    • 键隐式地保存在结点里

TrieST 代码

public class TrieST<Value> {
    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<String> keys() {
        return keysWithPrefix("");
    }

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

    private void collect(Node x, String pre, Queue<String> 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<String> keysThatMatch(String pat) {
        Queue<String> q = new Queue<String>();
        collect(root, "", pat, q);
        return q;
    }

    public void collect(Node x, String pre, String pat, Queue<String> 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<Value> {
    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;
    }
}
相关文章
相关标签/搜索