DoubleArrayTrie 学习

  1. 源码解读 —— darts-java
DoubleArrayTrie dat = new DoubleArrayTrie();
/**
words 是String 数组类型按照字典序排好,类似于下面的形式
一举
一举一动
一举成名
一举成名天下知
万能
万能胶
**/
dat.build(words)

2.DoubleArrayTrie 的具体实现

public class DoubleArrayTrie {

    public int build(List<String> _key, int _length[], int _value[],int _keySize) {
         if (_keySize > _key.size() || _key == null)
            return 0;

        // progress_func_ = progress_func;
        key = _key;
        length = _length;
        keySize = _keySize;
        value = _value;
        progress = 0;
        // check和Base数组的创建
        resize(65536 * 32);

        base[0] = 1;
        nextCheckPos = 0;

        Node root_node = new Node();
        root_node.left = 0;
        root_node.right = keySize;
        root_node.depth = 0;

        List<Node> siblings = new ArrayList<Node>();
        fetch(root_node, siblings);
        insert(siblings);

        // size += (1 << 8 * 2) + 1; // ???
        // if (size >= allocSize) resize (size);

        used = null;
        key = null;

        return error_;




    }

}
2.1 Base 和 check 数组的新建和扩容
private int allocSize;

  /** 创建 base 和 check 数组**/
  private int check[];
  private int base[];
  private boolean used[];

   private int resize(int newSize) {
        int[] base2 = new int[newSize];
        int[] check2 = new int[newSize];
        boolean used2[] = new boolean[newSize];
        if (allocSize > 0) {
             // 对原数组进行扩容
            System.arraycopy(base, 0, base2, 0, allocSize);
            System.arraycopy(check, 0, check2, 0, allocSize);
            System.arraycopy(used2, 0, used2, 0, allocSize);
        }

        base = base2;
        check = check2;
        used = used2;

        return allocSize = newSize;
    }



    public int build(List<String> key) {
        return build(key, null, null, key.size());
    }

讲解下面的方法使用这样的一个例子 :假设要插入的 模式为: { AC,ACE,ACFF,AD,CD,CF,ZQ }(注意:这些模式串是按照顺序排好的)

 1、由code= ASCII+1, 以及i = base[0] + code可以得到下面每个字符的状态:

   root  A   C   D   E   F   Q   Z

i 0 67 69 92
code 0 66 68 69 70 71 82 91

2.2或取到某个节点的所有子节点
/** 获取到子节点
以上面的例子为例 , 第一次 parent {left:0, right:key.length,depth:0}



 **/
 private int fetch(Node parent, List<Node> siblings) {
        if (error_ < 0)
            return 0;

        int prev = 0;
        /**在第一轮中 left: right:key.length */
        for (int i = parent.left; i < parent.right; i++) {
             /**如果处理当前的字符串长度小于depth 不做处理**/
            if ((length != null ? length[i] : key.get(i).length()) < parent.depth)
                continue;

            String tmp = key.get(i);

            int cur = 0;
            if ((length != null ? length[i] : tmp.length()) != parent.depth)
               /**取到在该深度上的字符**/
                cur = (int) tmp.charAt(parent.depth) + 1;
            /**因为是按照字典序排序的上一个应该是小于等于cur**/
            if (prev > cur) {
                error_ = -3;
                return 0;
            }

            /**当出现了与上一个不同的字符或者是第一次**/
            if (cur != prev || siblings.size() == 0) {
                /**新建一个节点**/
                Node tmp_node = new Node();
                /**该节点是父节点的子节点,所以深度+1 **/
                tmp_node.depth = parent.depth + 1;
                tmp_node.code = cur;
                /**从i号开始出现了新的节点**/
                tmp_node.left = i;

                if (siblings.size() != 0)
                  /**如果已经存在其他不同的节点了,所以上一个节点的范围到目前节点的索引为止**/
                    siblings.get(siblings.size() - 1).right = i;
                // 添加新的节点
                siblings.add(tmp_node);
            }

            /**更新上一个节点的值**/
            prev = cur;
        }

        /**如果prev == cur 即没有出现不同的节点**/
        if (siblings.size() != 0)
            /**最后一个节点的right 永远和父节点的right相同**/
            siblings.get(siblings.size() - 1).right = parent.right;
        // 返回子节点的个数
        return siblings.size();
    }

对于上面的例子来说 :
第一轮的输出是: Node{A,66,left:0,right:2} ,Node{C,68,left:3,right:4}
Node{Z,91,left:6,right:6}
第二轮 : 同上,此时获得A的子节点为{C,D}

2.3 获取到子节点后就像需要将子节点插入到 Base和 check数组中
private int insert(List<Node> siblings) {
        if (error_ < 0)
            return 0;

        int begin = 0;
        int pos = ((siblings.get(0).code + 1 > nextCheckPos) ? siblings.get(0).code + 1
                : nextCheckPos) - 1;

        int nonzero_num = 0;
        int first = 0;

        if (allocSize <= pos)
            resize(pos + 1);

       /**
          这个大循环的作用是找到一个数begin 使得每一个子节点的字符的code 在即上begin对应的check的数组位置为0 即
          for ( child.code in  all_child) {
               check[begin+child.code] = 0
          }
     与此同时begin的值不能重复,所以使用一个boolean use[] 来表示某一个begin 是否已经被使用了
      **/
        outer: while (true) {
            pos++;

            if (allocSize <= pos)
                resize(pos + 1);
             /**pos 的作用是从第一个子节点的code出开始寻找到check[pos] =0 的位置**/
            if (check[pos] != 0) {
                nonzero_num++;
                continue;
            } else if (first == 0) {
                nextCheckPos = pos;
                first = 1;
            }
            /**找的一个begin的值是第一个子节点的check[code+begin]=0**/
            begin = pos - siblings.get(0).code;

            if (allocSize <= (begin + siblings.get(siblings.size() - 1).code)) {
                // progress can be zero
                double l = (1.05 > 1.0 * keySize / (progress + 1)) ? 1.05 : 1.0
                        * keySize / (progress + 1);
                resize((int) (allocSize * l));
            }

            /**判断这个begin是否已经用过了**/
            if (used[begin])
                continue;
            /**判断这个begin是否可以使所用的子节点都满足
              check[code+begin] = 0 **/
            for (int i = 1; i < siblings.size(); i++)
                if (check[begin + siblings.get(i).code] != 0)
                     /**只要有一个不满足就重读循环再找新的begin**/
                    continue outer;

            break;
        }

        if (1.0 * nonzero_num / (pos - nextCheckPos + 1) >= 0.95)
            nextCheckPos = pos;
        /**标志这个begin已经被使用**/
        used[begin] = true;
        size = (size > begin + siblings.get(siblings.size() - 1).code + 1) ? size
                : begin + siblings.get(siblings.size() - 1).code + 1;

        /**设置每一个子节点的check[code+begin] = begin **/
        for (int i = 0; i < siblings.size(); i++)
            check[begin + siblings.get(i).code] = begin;
        /**这是一个递调用来的插入数据**/
        for (int i = 0; i < siblings.size(); i++) {
            List<Node> new_siblings = new ArrayList<Node>();

            if (fetch(siblings.get(i), new_siblings) == 0) {
              /**如果该节点没有子节点则该节点是叶子节点,做特殊处理**/
                base[begin + siblings.get(i).code] = (value != null) ? (-value[siblings
                        .get(i).left] - 1) : (-siblings.get(i).left - 1);

                if (value != null && (-value[siblings.get(i).left] - 1) >= 0) {
                    error_ = -2;
                    return 0;
                }

                progress++;
                // if (progress_func_) (*progress_func_) (progress,
                // keySize);
            } else {
                /**不是叶子节点递归插入**/
                int h = insert(new_siblings);
                base[begin + siblings.get(i).code] = h;
            }
        }
        return begin;


}

具体的插入过程可以参考 DoubleArrayTrie : DAT双数组Trie树

3. 查找

3.1 相同前缀查找
public List<Integer> commonPrefixSearch(String key) {
        return commonPrefixSearch(key, 0, 0, 0);
    }

    public List<Integer> commonPrefixSearch(String key, int pos, int len,
            int nodePos) {
            // len 查找的长度,默认查找整个字符串
        if (len <= 0)
            len = key.length();
       // nodePose 从哪个节点开始查找,默认从根节点
        if (nodePos <= 0)
            nodePos = 0;

        List<Integer> result = new ArrayList<Integer>();

        char[] keyChars = key.toCharArray();

        int b = base[nodePos];
        int n;
        int p;

        for (int i = pos; i < len; i++) {
            p = b;
            n = base[p];
            // 确定是否是叶子节点
            if (b == check[p] && n < 0) {
                result.add(-n - 1);
            }

            p = b + (int) (keyChars[i]) + 1;
            if (b == check[p])
                b = base[p];
            else
                return result;
        }

        /**查找玩整个字符串后,判断这一整个字符串是否是在trie如果是的话,也把这个字符串作为一自己有共同前缀的字符串返回**/
        p = b;
        n = base[p];

        if (b == check[p] && n < 0) {
        //叶子节点的值为 -siblings.get(i).left - 1
            result.add(-n - 1);
        }

        return result;
    }
2 . 完全查找
public int exactMatchSearch(String key) {
        return exactMatchSearch(key, 0, 0, 0);
    }

    public int exactMatchSearch(String key, int pos, int len, int nodePos) {
        if (len <= 0)
            len = key.length();
        if (nodePos <= 0)
            nodePos = 0;

        int result = -1;

        char[] keyChars = key.toCharArray();

        int b = base[nodePos];
        int p;

        for (int i = pos; i < len; i++) {
            p = b + (int) (keyChars[i]) + 1;
            if (b == check[p])
                b = base[p];
            else
                return result;
        }

        p = b;
        int n = base[p];
        if (b == check[p] && n < 0) {
            result = -n - 1;
        }
        return result;
    }

参考资料

DoubleArrayTrie

深入双数组Trie(Double-Array Trie)

DoubleArrayTrie : DAT双数组Trie树

双数组Tire树简介

Double Array Trie

双数组Trie树(DoubleArrayTrie)Java实现

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值