字符串匹配算法(Trie树)

文章目录

    • 1. Trie树概念
    • 2. Trie树操作
      • 2.1 存储
      • 2.2 查找
      • 2.3 插入
      • 2.4 删除
      • 2.5 打印
    • 3. 完整代码
    • 4. Trie树与散列表、红黑树的比较
      • 4.1 思考题
      • 参考文章
    • 5. 练习题

1. Trie树概念

  • Trie树,也叫字典树,它是一个树形结构。是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串。
  • Trie树本质,利用字符串之间的公共前缀,将重复的前缀合并在一起。
    在这里插入图片描述

2. Trie树操作

2.1 存储

Trie树是一个多叉树;二叉树的数据结构里存放着左右子节点的指针;
Trie树采用的一种经典的存储方式是散列表
在这里插入图片描述

class TrieNode//Trie树节点类,假设只有26个字母的数据集
{
public:char data;TrieNode *children[charNum];size_t count;//记录这个节点被多少个单词占用bool isEndOfWord;//是否是一个单词的结束字符size_t freq;    //单词插入的频次TrieNode(char ch = '/'):data(ch), isEndOfWord(false), count(0), freq(0){memset(children,0,sizeof(TrieNode*) * charNum);}~TrieNode(){}
};

Trie树比较浪费内存,children数组存放指针;
牺牲点效率的话,可以将数组改成,有序数组,跳表,散列表,红黑树等

2.2 查找

TrieNode* find_private(const string &text) const//查找某个字符串,返回最后一个字符节点的指针
{TrieNode *p = root;int index;for(int i = 0; i < text.size(); ++i){index = text[i] - 'a';if(p->children[index] == NULL)return NULL;//还没匹配完p = p->children[index];}if(p->isEndOfWord == false)//匹配完,但是只是前缀return NULL;else{return p;//私有find无输出信息}
}

时间复杂度O(k),k为要查找的字符串长度

2.3 插入

void insert(const string &text)//插入一个字符串
{TrieNode *p = find_private(text);if(p)//找到了字符串,不用插入,频次加1{p->freq++;return;}p = root;int index;for(int i = 0; i < text.size(); ++i){index = text[i] - 'a';if(p->children[index] == NULL){TrieNode *newNode = new TrieNode(text[i]);p->children[index] = newNode;}p->count++;p = p->children[index];}p->count++;p->freq++;p->isEndOfWord = true;
}

时间复杂度O(n),n为所有字符串长度和

2.4 删除

bool delString(const string &text)
{TrieNode *p = root;stack<TrieNode*> nodeStack;nodeStack.push(root);int index;for(int i = 0; i < text.size(); ++i){index = text[i] - 'a';if(p->children[index] == NULL)return false;//还没匹配完p = p->children[index];nodeStack.push(p);}if(p->isEndOfWord == false)//匹配完,但是只是前缀return false;else{while(nodeStack.top()->count == 1)//删除单词只要自己包含的部分{index = nodeStack.top()->data - 'a';
//            cout << "del char: " << nodeStack.top()->data << endl;//(调试代码)delete nodeStack.top();nodeStack.pop();}nodeStack.top()->children[index] = NULL;//断开已删除的部分while(!nodeStack.empty()){nodeStack.top()->count--;//单词占用记录减1nodeStack.pop();}return true;}
}

析构函数

void destory(TrieNode* proot)//树不再使用,结束前,释放资源
{if(proot == NULL){return;}for(int i = 0; i < charNum; ++i){destory(proot->children[i]);}delete proot;proot = NULL;
}

2.5 打印

	void printStrWithPre(const string prefix) const//打印有指定前缀的单词{if(prefix.size() == 0)return;TrieNode *p = root;int index,printID = 0;for(int i = 0; i < prefix.size(); ++i){index = prefix[i] - 'a';if(p->children[index] == NULL)//前缀还没匹配成功{cout << "-------------------------" << endl;cout << "no string with prefix: " << prefix << " can be found!" << endl;return;}elsep = p->children[index];}//匹配完了,p指向前缀最后一个字符节点cout << "-------------------------" << endl;cout << p->count << " string(s) with prefix: " << prefix << " , as following:" << endl;printWordsOfNode(p,prefix,printID);cout << "-----------end-----------" << endl;}void printDict() const//字典序输出全部单词{string word("");int printID = 0;cout << "-------------------------" << endl;cout << "all " << itemCount() << " words as following:" << endl;printWordsOfNode(root,word,printID);cout << "-----------end-----------" << endl;}
private:void printWordsOfNode(TrieNode* p, string prefix, int &order) const{//递归打印前缀最后一个字符对应节点下面所有的字符if(p != NULL){if(p->isEndOfWord)//是终止字符,prefix是不断+出来的,是整个字符串cout << ++order << " " << prefix << ", frequency: " << p->freq << endl;for(int i = 0; i < charNum; ++i){if(p->children[i] != NULL)printWordsOfNode(p->children[i],prefix+(p->children[i]->data),order);}}}

3. 完整代码

https://github.com/hitskyer/course/blob/master/dataAlgorithm/chenmingming/string_matching/trie.cpp

/*** @description: trie树,字典树* @author: michael ming* @date: 2019/6/24 19:00* @modified by: */
#include <iostream>
#include <cstring>
#include <stack>
#define charNum 26
using namespace std;
class TrieNode//Trie树节点类,假设只有26个字母的数据集
{
public:char data;TrieNode *children[charNum];size_t count;//记录这个节点被多少个单词占用bool isEndOfWord;//是否是一个单词的结束字符size_t freq;    //单词插入的频次TrieNode(char ch = '/'):data(ch), isEndOfWord(false), count(0), freq(0){memset(children,0,sizeof(TrieNode*) * charNum);}~TrieNode(){}
};
class Trie
{
public:TrieNode* root;Trie(){root = new TrieNode;}~Trie(){destory(root);}void insert(const string &text)//插入一个字符串{TrieNode *p = find_private(text);if(p)//找到了字符串,不用插入,频次加1{p->freq++;return;}p = root;int index;for(int i = 0; i < text.size(); ++i){index = text[i] - 'a';if(p->children[index] == NULL){TrieNode *newNode = new TrieNode(text[i]);p->children[index] = newNode;}p->count++;p = p->children[index];}p->count++;p->freq++;p->isEndOfWord = true;}void find(const string &text) const//查找某个字符串{TrieNode *p = root;int index;for(int i = 0; i < text.size(); ++i){index = text[i] - 'a';if(p->children[index] == NULL)//还没匹配完{cout << "can not find string: " << text << endl;return;}p = p->children[index];}if(p->isEndOfWord == false)//匹配完,但是只是前缀{cout << "can not find string: " << text << endl;return;}else{cout << text << " occurs " << p->freq << " time(s)." << endl;return;}}private:TrieNode* find_private(const string &text) const//查找某个字符串,返回最后一个字符节点的指针{TrieNode *p = root;int index;for(int i = 0; i < text.size(); ++i){index = text[i] - 'a';if(p->children[index] == NULL)return NULL;//还没匹配完p = p->children[index];}if(p->isEndOfWord == false)//匹配完,但是只是前缀return NULL;else{return p;//私有find无输出信息}}public:void destory(TrieNode* proot)//树不再使用,结束前,释放资源{if(proot == NULL){return;}for(int i = 0; i < charNum; ++i){destory(proot->children[i]);}delete proot;proot = NULL;}bool delString(const string &text){TrieNode *p = root;stack<TrieNode*> nodeStack;nodeStack.push(root);int index;for(int i = 0; i < text.size(); ++i){index = text[i] - 'a';if(p->children[index] == NULL)return false;//还没匹配完p = p->children[index];nodeStack.push(p);}if(p->isEndOfWord == false)//匹配完,但是只是前缀return false;else{while(nodeStack.top()->count == 1)//删除单词只要自己包含的部分{index = nodeStack.top()->data - 'a';
//                cout << "del char: " << nodeStack.top()->data << endl;//(调试代码)delete nodeStack.top();nodeStack.pop();}nodeStack.top()->children[index] = NULL;//断开已删除的部分while(!nodeStack.empty()){nodeStack.top()->count--;//单词占用记录减1nodeStack.pop();}return true;}}size_t itemCount() const//字典中单词种数{return root->count;}void printStrWithPre(const string prefix) const//打印有指定前缀的单词{if(prefix.size() == 0)return;TrieNode *p = root;int index,printID = 0;for(int i = 0; i < prefix.size(); ++i){index = prefix[i] - 'a';if(p->children[index] == NULL)//前缀还没匹配成功{cout << "-------------------------" << endl;cout << "no string with prefix: " << prefix << " can be found!" << endl;return;}elsep = p->children[index];}//匹配完了,p指向前缀最后一个字符节点cout << "-------------------------" << endl;cout << p->count << " string(s) with prefix: " << prefix << " , as following:" << endl;printWordsOfNode(p,prefix,printID);cout << "-----------end-----------" << endl;}void printDict() const//字典序输出全部单词{string word("");int printID = 0;cout << "-------------------------" << endl;cout << "all " << itemCount() << " words as following:" << endl;printWordsOfNode(root,word,printID);cout << "-----------end-----------" << endl;}
private:void printWordsOfNode(TrieNode* p, string prefix, int &order) const{//递归打印前缀最后一个字符对应节点下面所有的字符if(p != NULL){if(p->isEndOfWord)//是终止字符,prefix是不断+出来的,是整个字符串cout << ++order << " " << prefix << ", frequency: " << p->freq << endl;for(int i = 0; i < charNum; ++i){if(p->children[i] != NULL)printWordsOfNode(p->children[i],prefix+(p->children[i]->data),order);}}}
};
int main()
{Trie textlib;string a("hello"), b("her"), c("so"), d("hi"), e("how"), f("see");textlib.insert(a);textlib.insert(a);textlib.insert(b);textlib.insert(c);textlib.insert(d);textlib.insert(e);textlib.insert(f);textlib.find(a);textlib.find(b);textlib.find(d);textlib.printStrWithPre("h");textlib.printDict();textlib.delString("hello");textlib.find(a);textlib.printStrWithPre("h");textlib.printDict();cout << "total kind(s) of word: " << textlib.itemCount() << endl;return 0;
}

在这里插入图片描述

4. Trie树与散列表、红黑树的比较

Trie树对要处理的字符串有及其严苛的要求。

  • 第一,字符串中包含的字符集不能太大。如果字符集太大,那存储空间可能就会浪费很多。即便可以优化,也要付出牺牲查询、插入效率的代价。
  • 第二,要求字符串的前缀重合比较多,不然空间消耗会变大很多。
  • 第三,如果要用Trie树解决问题,那我们就要自己从零开始实现一个Trie树,还要保证没有bug,这个在工程上是将简单问题复杂化,除非必须,一般不建议这样做。
  • 第四,通过指针串起来的数据块是不连续的,而Trie树中用到了指针,所以,对缓存并不友好,性能上会打个折扣。
  • 综合这几点,针对在一组字符串中查找字符串的问题,工程中更倾向于用散列表或者红黑树。因为这两种数据结构,我们都不需要自己去实现,直接利用编程语言中提供的现成类库就行了。
  • Trie 树只是不适合精确匹配查找,这种问题更适合用散列表或者红黑树来解决。
  • Trie树比较适合的是查找前缀匹配的字符串,例如搜索引擎智能匹配输入,给出候选提示(如果有多个候选,可以按搜索热度排序,上面代码里面的 frequency)。
    在这里插入图片描述
  • Trie树还可以应用于自动输入补全(输入法,代码编辑器,浏览器网址输入)

4.1 思考题

  • 上面针对英文的搜索关键词,对于更加复杂的中文来说,词库中的数据又该如何构建成Trie 树呢?
  • 如果词库中有很多关键词,在搜索提示的时候,用户输入关键词,作为前缀在Trie 树中可以匹配的关键词也有很多,如何选择展示哪些内容呢?(按搜索热度或者概率)
  • 像Google 这样的搜索引擎,用户单词拼写错误的情况下,Google还是可以使用正确的拼写来做关键词提示,这个又是怎么做到的呢?

参考文章

https://www.cnblogs.com/xujian2014/p/5614724.html

5. 练习题

LeetCode 1707. 与数组中元素的最大异或值(Trie树)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/480208.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文浅尝 | 基于知识图谱嵌入的 Bootstrapping 实体对齐方法

来源: IJCAI 2018链接: https://www.ijcai.org/proceedings/2018/0611.pdf本文关注基于知识图谱嵌入(后文全部简称为知识嵌入)的实体对齐工作&#xff0c;针对知识嵌入训练数据有限这一情况&#xff0c;作者提出一种 bootstrapping 策略&#xff0c;迭代标注出可能的实体对齐&a…

大规模领域词汇库项目DomainWordsDict:涵盖68个领域、共计916万的词汇库资源开放

项目概述 DomainWordsDict, Chinese words dict that contains more than 68 domains, which can be used as text classification、knowledge enhance task。涵盖68个领域、共计916万词的专业词典知识库&#xff0c;可用于文本分类、知识增强、领域词汇库扩充等自然语言处理应…

递归」与「动态规划

原文地址&#xff1a;https://juejin.im/post/5c2308abf265da615304ce41#heading-8 在学习「数据结构和算法」的过程中&#xff0c;因为人习惯了平铺直叙的思维方式&#xff0c;所以「递归」与「动态规划」这种带循环概念&#xff08;绕来绕去&#xff09;的往往是相对比较难以…

当知识图谱遇上推荐系统之DKN模型(论文笔记一)

Deep Knowledge-Aware Network for News Recommendation 类别&#xff1a;依次学习 首先使用知识图谱特征学习得到实体向量和关系向量&#xff0c;然后将这些低维向量引入推荐系统&#xff0c;学习得到用户向量和物品向量。 [论文下载链接]https://arxiv.org/abs/1801.08284…

POJ 1936 字符匹配(水题)

题目链接&#xff1a; http://poj.org/problem?id1936 题目大意&#xff1a; 给定字符a&#xff0c;b&#xff0c;问b中去掉一些字符后能不能得到a 解题思路&#xff1a; 暴力从前往后扫描一遍即可。 AC代码&#xff1a; /*** description: poj1936水题* author: michael…

领域应用 | 从数据到智慧,知识图谱如何推动金融更智能?

本文转载在公众号&#xff1a;恒生技术之眼。在《人工智能知识图谱&#xff1a;如何规整海量金融大数据&#xff1f;》一文中&#xff0c;笔者曾提到&#xff0c;面向人工智能的大数据治理&#xff0c;势必能有效支撑智能金融从感知智能向认知智能变革。这是因为目前在资本市场…

2021届秋招算法岗真的要灰飞烟灭了吗?

星标/置顶小屋&#xff0c;带你解锁最萌最前沿的NLP、搜索与推荐技术文 | 不拖更的夕小瑶2014年末入坑AI&#xff0c;一路见证了AI行业的快速起飞、爆炸、焦虑和冷却。小夕前几天在知乎上看到一个问题《如何看待2021年秋招算法岗灰飞烟灭》被顶上了热榜。有点感叹&#xff0c;怎…

万字长文:近年来学界、业界视角下的“事理图谱”发展总结与思考

一、引言 大部分技术都会经历从提出&#xff0c;到验证&#xff0c;再到修正&#xff0c;再到落地的这样一个过程。事理图谱这个概念从国内学者自2017年提出到现在&#xff0c;已经经历了近4年的时间&#xff0c;那么在这四年的时间里&#xff0c;事理图谱目前处于一个什么…

Redis系列教程(二):详解Redis的存储类型、集群架构、以及应用场景

高并发架构系列 高并发架构系列&#xff1a;数据库主从同步的3种一致性方案实现&#xff0c;及优劣比较 高并发架构系列&#xff1a;Spring Cloud的核心成员、以及架构实现详细介绍 高并发架构系列&#xff1a;服务注册与发现的实现原理、及实现优劣势比较 高并发架构系列&a…

当知识图谱遇上推荐系统之PippleNet模型(论文笔记二)

RippleNet | Propagating User Preferences on the Knowledge 类别&#xff1a;联合学习 将知识图谱特征学习和推荐算法的目标函数结合&#xff0c;使用端到端&#xff08;end-to-end&#xff09;的方法进行联合学习。 [论文下载链接]https://arxiv.org/abs/1803.03467 1、…

POJ 3690 找星座(2D匹配)(未解答)

文章目录1. 题目信息1.1 题目链接1.2 题目大意1.3 解题思路2. 代码2.1 Time Limit Exceeded 代码2.2 Time Limit Exceeded 代码2.3 Time Limit Exceeded 代码1. 题目信息 1.1 题目链接 http://poj.org/problem?id3690 1.2 题目大意 给定大的矩阵&#xff08;天空的样子&am…

综述 | 事件抽取及推理 (上)

本文转载自公众号&#xff1a;知识工场。 事件概要事件是一种重要的知识&#xff0c;近年来&#xff0c;越来越多的工作关注于从开放域或领域文本中抽取结构化事件知识。同时&#xff0c;除了本身就很困难的…

下载 | 李宏毅:1 天搞懂深度学习,我总结了 300 页 PPT

《1 天搞懂深度学习》&#xff0c;300 多页的 ppt&#xff0c;台湾李宏毅教授写的&#xff0c;非常棒。不夸张地说&#xff0c;是我看过最系统&#xff0c;也最通俗易懂的&#xff0c;关于深度学习的文章。这份 300 页的 PPT&#xff0c;被搬运到了 SlideShare 上&#xff0c;下…

史上最全Redis面试49题(含答案):哨兵+复制+事务+集群+持久化等

最全面试题答案系列 史上最强多线程面试44题和答案&#xff1a;线程锁线程池线程同步等 最全MySQL面试60题和答案 史上最全memcached面试26题和答案 史上最全Spring面试71题与答案 今天主要分享redis最全答案系列 Redis主要有哪些功能&#xff1f; 1.哨兵&#xff08;Sen…

DTW动态时间规整算法

原文地址&#xff1a;https://blog.csdn.net/qcyfred/article/details/53824507 https://zhuanlan.zhihu.com/p/43247215 动态时间规整&#xff08;DTW&#xff09;算法简介相忘天涯&#xff0c;深藏于心19 人赞同了该文章DTW最初用于识别语音的相似性。我们用数字表示音调高低…

POJ 3461 字符串匹配(KMP / 哈希(有推导))

文章目录1. 题目1.1 题目链接1.2 题目大意2. Accepted代码2.1 KMP解法2.2 哈希法&#xff08;有推导过程&#xff09;1. 题目 1.1 题目链接 http://poj.org/problem?id3461 类似题目&#xff1a;LeetCode 30. 串联所有单词的子串&#xff08;字符串哈希&#xff09; 1.2 题…

莫比乌斯:百度凤巢下一代广告召回系统

星标/置顶小屋&#xff0c;带你解锁最萌最前沿的NLP、搜索与推荐技术文 | 江城编 | 夕小瑶今天聊聊百度在最顶级的数据挖掘会议KDD2019的计算广告track上提出的query-ad匹配模型——莫比乌斯&#xff08;MOBIUS&#xff09;。这也是百度凤巢下一代广告召回系统的内部代号&#…

当知识图谱遇上推荐系统之MKR模型(论文笔记三)

Multi-Task Feature Learning for Knowledge Graph Enhanced Recommendation 类别&#xff1a;交替学习 将知识图谱特征学习和推荐算法视为两个分离但又相关的任务&#xff0c;使用多任务学习的框架进行交替学习。 1、背景 MKR是一个通用的、端对端的深度推荐框架&#xf…

关于话题演化关系网络生成的路线思考:从话题聚类到话题网络展示

话题演化关系网络生成&#xff0c;是实现事件演化追踪的一个重要方法。通过对文本话题进行聚类、内容处理、话题演化关联、话题演化网络的展示&#xff0c;能够在一定程度上为用户揭示出一个事件发展的情况。本文就笔者对该方向的实现路线思考进行总结&#xff0c;分享给大家。…

综述 | 事件抽取及推理 (下)

本文转载在公众号&#xff1a;知识工场 。 上篇事件抽取及推理的推文已经介绍了事件抽取的基本方法&#xff0c;本篇主要介绍事件推理的相关工作。就目前来看&#xff0c;事件方向相关的研究还是以事件抽取为主流任务&#xff0c;当前大多都是在模型的框架和优化方面进行研究。…