1.Trie树简介
Trie树又称单词查找树,是一一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。
2.基本性质
它有3个基本性质:
(1)根节点不包含字符,除根节点外每一个节点都只包含一个字符;
(2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
(3)每个节点的所有子节点包含的字符都不相同。
3.基本操作
它有3个基本性质:
(1)根节点不包含字符,除根节点外每一个节点都只包含一个字符;
(2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
(3)每个节点的所有子节点包含的字符都不相同。
其基本操作有:查找、插入和删除,当然删除操作比较少见。查找Trie的方法为:
(1)从根结点开始一次搜索;
(2)取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
(3)在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。
(4)迭代过程……
(5)在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。
其他操作类似。
4.时间复杂度分析
(1)建立词典的过程。需要将所有的单词加入到字典树中,假设单词的平均长度为m,词典规模为n个词,那么建立词典耗费的时间为O(m*n),由于该过程可能涉及文件IO,因此速度可能较慢。
(2)查询过程。查询过程只需要按照指定的字符串逐级向下搜索即可,时间复杂度为O(m)。
(3)删除过程。同查询过程类似,时间复杂度为O(m)。
下图为对10w个字符串进行建立Trie树和搜索某个字符串的结果,该过程查询操作仅用了不到1ms。同快速排序+二分法查找法相比(查找耗时20ms),性能大大提高。
(1)建立词典的过程。需要将所有的单词加入到字典树中,假设单词的平均长度为m,词典规模为n个词,那么建立词典耗费的时间为O(m*n),由于该过程可能涉及文件IO,因此速度可能较慢。
(2)查询过程。查询过程只需要按照指定的字符串逐级向下搜索即可,时间复杂度为O(m)。
(3)删除过程。同查询过程类似,时间复杂度为O(m)。
下图为对10w个字符串进行建立Trie树和搜索某个字符串的结果,该过程查询操作仅用了不到1ms。同快速排序+二分法查找法相比(查找耗时20ms),性能大大提高。
5.源代码
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib> //函数srand()
#include <ctime> //函数time()
#include <vector>
#include <sys/time.h>
#include <algorithm>
#include <cstring>
using namespace std;
const long LEN = 100000; //词典的规模
const int MAX = 30; //词条的最大长度
const int MIN = 10; //词条的最小长度
int limit_value = 25; //进行插入排序的阈值
const int Son_Num = 26;
//---------------------Trie树的定义----------------------------
//节点定义
class Node{
public:
int flag; //节点的是否有单词出现的标记
int count; //单词出现的次数
Node* son[Son_Num]; //指向分支节点的指针,这里为26个
public:
Node() : flag(0),count(0){
memset(son, NULL, sizeof(Node*) * Son_Num);
}
};
//trie树的操作定义
class Trie{
private:
Node* root;
public:
Trie();
~Trie();
void insert(string str);
bool search(string str, int &count);
bool remove(string str);
void destory(Node* root);
};
//构造函数
Trie::Trie(){
root = new Node();
}
//析构函数
Trie::~Trie(){
destory(root);
}
void Trie::insert(string str){
int index = 0;
Node* pNode = root;
//不断向下搜索单词的字符是否出现
for(int i = 0; i < str.size(); i++){
index = str[i] - 'a';
//字符在规定的范围内时,才进行检查
if(index >= 0 && index < Son_Num){
//父节点是否有指针指向本字符
if(NULL == pNode -> son[index]){
pNode -> son[index] = new Node();
}
//指针向下传递
pNode = pNode -> son[index];
}else{
return;
}
}
//判断单词是否出现过
if(pNode -> flag == 1){
//单词已经出现过,计数器加1
pNode -> count++;
return;
}else{
//单词没有出现过,标记为出现,计数器加1
pNode -> flag = 1;
pNode -> count++;
}
}
//搜索指定单词,并返回单词出现次数(如果存在)
bool Trie::search(string str, int &count){
Node* pNode = root;
int index = 0;
int i = 0;
while(pNode != NULL && i < str.size()){
index = str[i] - 'a';
if(index >= 0 && index < Son_Num){
//字符在指定的范围内时
pNode = pNode -> son[index];
i++;
}else{
//字符不再指定的范围内
return false;
}
}
//判断字符串是否出现过
if(pNode != NULL && pNode -> flag == 1){
count = pNode -> count;
return true;
}else{
return false;
}
}
//删除指定的单词
bool Trie::remove(string str){
Node* pNode = root;
int i = 0;
int index = 0;
while(pNode != NULL && i < str.size()){
index = str[i] - 'a';
if(index >= 0 && index < Son_Num){
pNode = pNode -> son[index];
i++;
}else{
return false;
}
}
if(pNode != NULL && pNode -> flag == 1){
pNode -> flag = 0;
pNode -> count--;
return true;
}else{
return false;
}
}
//销毁Trie树
void Trie::destory(Node* root){
if(NULL == root){
return;
}
for(int i = 0; i < Son_Num; i++){
destory(root -> son[i]);
}
delete root;
root == NULL;
}
//-------------------------------------------------------------
//生成随机字符串
string rand_string(int min, int max){
char a[MAX+1];
int len = rand()%(max - min) + min;
for(int i = 0; i < len; i++){
a[i] = rand()%26 + 'a';
}
a[len] = '\0';
string str(a);
return str;
}
//将字符串写入文件,构建词典
void write_dict(){
ofstream fout;
fout.open("dict.txt");
srand((unsigned)time(NULL));
for(int i = 0; i < LEN; i++){
string str = rand_string(10, 30);
fout << str << '\n';
fout << flush;
}
fout.close();
}
//从文件中读取词典信息
vector<string> read_dict(){
ifstream fin;
fin.open("dict.txt");
string textline;
vector<string> dict;
while(getline(fin, textline, '\n')){
dict.push_back(textline);
}
return dict;
}
//-----------------测试代码---------------------------
long getCurrentTime(){
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec*1000*1000 + tv.tv_usec;
}
int main(){
Trie trie;
string str;
ifstream fin;
fin.open("dict.txt");
//建立Trie树
long time_1 = getCurrentTime();
while(getline(fin, str, '\n')){
trie.insert(str);
}
long time_2 = getCurrentTime();
fin.close();
//查找
str = "siubetamwm";
int count = -1;
long time_3 = getCurrentTime();
bool isFind = trie.search(str, count);
cout << "要查找的字符串:" << str << endl;
long time_4 = getCurrentTime();
if(isFind){
cout << " 该单词存在,存在次数:" << count << endl;
}else{
cout << " 该单词不存在!" << endl;
}
//删除
str = "lutgjrxjavgfkij";
cout << "要删除的字符串:" << str << endl;
bool isRemove = trie.remove(str);
if(isRemove){
cout << " 该单词存在,删除成功!" << endl;
}else{
cout << " 该单词不存在,删除失败!" << endl;
}
cout << "建立Trie树用时(纳秒):" << time_2 - time_1 << endl;
cout << "查找字符串用时(纳秒):" << time_4 - time_3 << endl;
}