Trie树(转)

在读这文章 10 小时到 10 分钟,一步步优化巨量关键词的匹配 时候发现的Trie,顺便学习了一下Trie树(前缀树)


定义
字典树,又称前缀树或 trie 树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。


设计
那么 trie 树怎么实现关键字的匹配呢? 这里以一幅图来讲解 trie 树匹配的过程。

Trie树(转)_第1张图片

构造trie树

  1. 将关键词用上面介绍的preg_split()函数拆分为单个字符。如科学家就拆分为科、学、家 三个字符。
  2. 在最后一个字符后添加一个特殊字符 `,此字符作为一个关键词的结尾(图中的粉红三角),以此字符来标识查到了一个关键词(不然,我们不知道匹配到科、学两个字符时算不算匹配成功)。
  3. 检查根部是否有第一个字符(科)节点,如果有了此节点,到步骤4。 如果还没有,在根部添加值为科的节点。依次检查并添加学、家 两个节点。
  4. 在结尾添加`节点,并继续下一个关键词的插入。

匹配
然后我们以 ·这位科学家很了不起!·为例来发起匹配。

  1. 首先我们将句子拆分为单个字符 这、位、...;
  2. 从根查询第一个字符·这·,并没有以这个字符开头的关键词,将字符“指针”向后移,直到找到根下有的字符节点科;
  3. 接着在节点·科·下寻找值为 ·学·节点,找到时,结果子树的深度已经到了2,关键词的最短长度是2,此时需要在·学·结点下查找是否有`,找到意味着匹配成功,返回关键词,并将字符“指针”后移,如果找不到则继续在此结点下寻找下一个字符。
  4. 如此遍历,直到最后,返回所有匹配结果。

代码
完整代码已经放到了GitHub上:Trie-GitHub-zhenbianshu,这里放上核心。

首先是数据结构树结点的设计,当然它也是重中之重:

 $node = array(
    'depth' => $depth, // 深度,用以判断已命中的字数
    'next' => array(
        $val => $node, // 这里借用php数组的哈希底层实现,加速子结点的查找
        ...
    ),
);

然后是树构建时子结点的插入:

// 这里要往节点内插入子节点,所以将它以引用方式传入
private function insert(&$node, $words) {
         if (empty($words)) {
            return;
        }
        $word = array_shift($words);
        // 如果子结点已存在,向子结点内继续插入
        if (isset($node['next'][$word])) {
            $this->insert($node['next'][$word], $words);
        } else {
            // 子结点不存在时,构造子结点插入结果
            $tmp_node = array(
                'depth' => $node['depth'] + 1,
                'next' => array(),
            );
            $node['next'][$word] = $tmp_node;
            $this->insert($node['next'][$word], $words);
        }
    }

最后是查询时的操作:

// 这里也可以使用一个全局变量来存储已匹配到的字符,以替换$matched
private function query($node, $words, &$matched) {
        $word = array_shift($words);
        if (isset($node['next'][$word])) {
            // 如果存在对应子结点,将它放到结果集里
            array_push($matched, $word);
            // 深度到达最短关键词时,即可判断是否到词尾了
            if ($node['next'] > 1 && isset($node['next'][$word]['next']['`'])) {
                return true;
            }
            return $this->query($node['next'][$word], $words, $matched);
        } else {
            $matched = array();
            return false;
        }
    }

备注:
由于本地测试文件是从MySQL导出到txt文件里,构造树时候导致fgets每次读取一行,内容里包含了\r\n,构造的树里不包含` 这个key,所以一直查询不到,只要在关键词append($keyword)之前处理一下即可,代码是

$keyword = trim($keyword, "\r\n");

From: http://www.cnblogs.com/zhenbianshu/p/7197349.html

你可能感兴趣的:(Trie树(转))