Trie树(c++实现)

转:http://www.cnblogs.com/kaituorensheng/p/3602155.html

http://blog.csdn.net/insistgogo/article/details/7828851

一、定义:

Trie,又称字典树,是一种用于快速检索的二十六叉树结构。典型的空间换时间

二、结构图:

三、原理:

       Trie把要查找的关键词看作一个字符序列,并根据构成关键词字符的先后顺序检索树结构;

特别地:和二叉查找树不同,在Trie树中,每个结点上并非存储一个元素。

四、性质:

0、利用串的公共前缀,节约内存

1、在trie树上进行检索总是始于根结点

2、根节点不包含字符,除根节点外的每一个节点都只代表一个字母,这里不是包含。

3、从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

4、每个节点的所有子节点包含的字符都不相同。

五、效率分析

0、当存储大量字符串时,Trie耗费的空间较少。因为键值并非显式存储的,而是与其他键值共享子串。

1、查询快。在trie树中查找一个关键字的时间和树中包含的结点数无关,而取决于组成关键字的字符数。对于长度为m的键值,最坏情况下只需花费O(m)的时间(对比:二叉查找树的查找时间和树中的结点数有关O(log2n)。)

2、如果要查找的关键字可以分解成字符序列且不是很长,利用trie树查找速度优于二叉查找树。

3、若关键字长度最大是5,则利用trie树,利用5次比较可以从265=11881376个可能的关键字中检索出指定的关键字。而利用二叉查找树至少要进行log2265=23.5次比较。

六、应用:用于字符串的统计与排序,经常被搜索引擎系统用于文本词频统计。

1、字典树在串的快速检索中的应用。

给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。在这道题中,我们可以用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。

2、字典树在“串”排序方面的应用

给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。

3.、字典树在最长公共前缀问题的应用

对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为最近公共祖先问题。

代码

原理

先看个例子,存储字符串abc、ab、abm、abcde、pm可以利用以下方式存储

上边就是Trie树的基本原理:利用字串的公共前缀来节省存储空间,最大限度的减少无谓的字串比较。

应用

Trie树又称单词查找树,典型的应用是用于统计,排序和保存大量的字符串(不仅用于字符串),所以经常被搜索引擎系统用于文本词频的统计。

设计

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

结点可以设计成这样:

class trieNode
{
    public:
        trieNode() : terminableSize(0), nodeSize(0) { for(int i = 0; i < Size; ++i) children[i] = NULL; }
        ~trieNode()
        {
            for(int i = 0; i < Size; ++i)
            {
                delete children[i];
                children[i] = NULL;
            }
        }
    public:
        int terminableSize;          //存储以此结点为结尾的字串的个数
        int nodeSize;                //记录此结点孩子的个数
        trieNode* children[Size];    //该数组记录指向孩子的指针
};

图示

设计成这样:

template<int Size, class Type>
class trie
{
    public:
        typedef trieNode<Size> Node;
        typedef trieNode<Size>* pNode;
        trie() : root(new Node) {}

        template<class Iterator>
        void insert(Iterator beg, Iterator end);
        void insert(const char *str);

        template<class Iterator>
        bool find(Iterator beg, Iterator end);
        bool find(const char *str);

        template<class Iterator>
        bool downNodeAlone(Iterator beg);

        template<class Iterator>
        bool erase(Iterator beg, Iterator end);
        bool erase(const char *str);

        int sizeAll(pNode);
        int sizeNoneRedundant(pNode);
    public:
        pNode root;
    private:
        Type index;
};

index字串索引利用(char % 26) 得到,这样‘a‘ % 26 = 19, ‘b‘ % 26 = 20

实现

插入

以插入abc、ab为例

]

删除

删除结点,首先查找此字串是否在树中,如果在树中,再查找此结点以下的部分是不是都是只有一个孩子,并且每个结点只有叶子结点是结束结点,如果不是继续往下重复上边过程。

统计字串个数

分两种情况

  1. 计算重复的字串的个数:是结束结点,此时加的是terminabel的个数
  2. 计算不重复的字串的个数:是结束结点,此时加的是1(当terminabel>0)的个数

参考代码

#include <iostream>
#include <cstring>
using namespace std;

template<int Size>
class trieNode
{
    public:
        trieNode() : terminableSize(0), nodeSize(0) { for(int i = 0; i < Size; ++i) children[i] = NULL; }
        ~trieNode()
        {
            for(int i = 0; i < Size; ++i)
            {
                delete children[i];
                children[i] = NULL;
            }
        }
    public:
        int terminableSize;
        int nodeSize;
        trieNode* children[Size];
};

template<int Size, class Type>
class trie
{
    public:
        typedef trieNode<Size> Node;
        typedef trieNode<Size>* pNode;
        trie() : root(new Node) {}

        template<class Iterator>
        void insert(Iterator beg, Iterator end);
        void insert(const char *str);

        template<class Iterator>
        bool find(Iterator beg, Iterator end);
        bool find(const char *str);

        template<class Iterator>
        bool downNodeAlone(Iterator beg);

        template<class Iterator>
        bool erase(Iterator beg, Iterator end);
        bool erase(const char *str);

        int sizeAll(pNode);
        int sizeNoneRedundant(pNode);
    public:
        pNode root;
    private:
        Type index;
};

template<int Size, class Type>
template<class Iterator>
void trie<Size, Type>::insert(Iterator beg, Iterator end)
{
    pNode cur = root;
    pNode pre;
    for(; beg != end; ++beg)
    {
        if(!cur->children[index[*beg]])
        {
            cur->children[index[*beg]] = new(Node);
            ++cur->nodeSize;
        }
        pre = cur;
        cur = cur->children[index[*beg]];
    }
    ++pre->terminableSize;
}
template<int Size, class Type>
void trie<Size, Type>::insert(const char *str)
{
    return insert(str, str + strlen(str));
}

template<int Size, class Type>
template<class Iterator>
bool trie<Size, Type>::find(Iterator beg, Iterator end)
{
    pNode cur = root;
    pNode pre;
    for(; beg != end; ++beg)
    {
        if(!cur->children[index[*beg]])
        {
            return false;
            break;
        }
        pre = cur;
        cur = cur->children[index[*beg]];
    }
    if(pre->terminableSize > 0)
        return true;
    return false;
}

template<int Size, class Type>
bool trie<Size, Type>::find(const char *str)
{
    return find(str, str + strlen(str));
}

template<int Size, class Type>
template<class Iterator>
bool trie<Size, Type>::downNodeAlone(Iterator beg)
{
    pNode cur = root;
    int terminableSum = 0;
    while(cur->nodeSize != 0)
    {
        terminableSum += cur->terminableSize;
        if(cur->nodeSize > 1)
            return false;
        else          //cur->nodeSize = 1
        {
            for(int i = 0; i < Size; ++i)
            {
                if(cur->children[i])
                    cur = cur->children[i];
            }
        }
    }
    if(terminableSum == 1)
        return true;
    return false;
}
template<int Size, class Type>
template<class Iterator>
bool trie<Size, Type>::erase(Iterator beg, Iterator end)
{
    if(find(beg, end))
    {
        pNode cur = root;
        pNode pre;
        for(; beg != end; ++beg)
        {
            if(downNodeAlone(cur))
            {
                delete cur;
                return true;
            }
            pre = cur;
            cur = cur->children[index[*beg]];
        }
        if(pre->terminableSize > 0)
            --pre->terminableSize;
        return true;
    }
    return false;
}

template<int Size, class Type>
bool trie<Size, Type>::erase(const char *str)
{
    if(find(str))
    {
        erase(str, str + strlen(str));
        return true;
    }
    return false;
}

template<int Size, class Type>
int trie<Size, Type>::sizeAll(pNode ptr)
{
    if(ptr == NULL)
        return 0;
    int rev = ptr->terminableSize;
    for(int i = 0; i < Size; ++i)
        rev += sizeAll(ptr->children[i]);
    return rev;
}

template<int Size, class Type>
int trie<Size, Type>::sizeNoneRedundant(pNode ptr)
{
    if(ptr == NULL)
        return 0;
    int rev = 0;
    if(ptr->terminableSize > 0)
        rev = 1;
    if(ptr->nodeSize != 0)
    {
        for(int i = 0; i < Size; ++i)
            rev += sizeNoneRedundant(ptr->children[i]);
    }
    return rev;
}

template<int Size>
class Index
{
    public:
        int operator[](char vchar)
        { return vchar % Size; }
};

int main()
{
    trie<26, Index<26> > t;
    t.insert("hello");
    t.insert("hello");
    t.insert("h");
    t.insert("h");
    t.insert("he");
    t.insert("hel");
    cout << "SizeALL:" << t.sizeAll(t.root) << endl;
    cout << "SizeALL:" << t.sizeNoneRedundant(t.root) << endl;
    t.erase("h");
    cout << "SizeALL:" << t.sizeAll(t.root) << endl;
    cout << "SizeALL:" << t.sizeNoneRedundant(t.root) << endl;
}

 结果 

 技术实现细节

1. 对树的删除,并不是树销毁结点,而是通过结点自身的析构函数实现

2. 模版类、模版函数、非类型模版可以参考:http://www.cnblogs.com/kaituorensheng/p/3601495.html

3. 字母的存储并不是存储的字母,而是存储的位置,如果该位置的指针为空,则说明此处没有字母;反之有字母。

4. terminableNum存储以此结点为结束结点的个数,这样可以避免删除时,不知道是否有多个相同字符串的情况。

时间: 2024-12-11 23:49:01

Trie树(c++实现)的相关文章

poj3630 Phone List (trie树模板题)

Phone List Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 26328   Accepted: 7938 Description Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of another. Let's say the phone catalogu

跳跃表,字典树(单词查找树,Trie树),后缀树,KMP算法,AC 自动机相关算法原理详细汇总

第一部分:跳跃表 本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈"跳跃表"的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类

Trie树学习2

数组实现的Trie树 字符容量有限,可以使用链表实现更为大容量的Trie #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <map> #include <set> #include <algorithm> #include <cstdlib> #

trie树(字典树)

1. trie树,又名字典树,顾名思义,它是可以用来作字符串查找的数据结构,它的查找效率比散列表还要高. trie树的建树: 比如有字符串"ab" ,"adb","adc"   可以建立字典树如图: 树的根节点head不存储信息,它有26个next指针,分别对应着字符a,b,c等.插入字符串ab时,next['a'-'a']即next[0]为空,这是申请一个结点放在next[0]的位置,插入字符串db时,next['d'-'a']即next[3]

Trie树

Trie树,即字典树或单词查找树,主要用于大量字符串的检索.去重.排序等操作. 主要原理就是利用字符串的公共前缀建立一棵多叉树,牺牲空间换取时间. 1 //Trie树 2 #include <iostream> 3 #include <string> 4 using std::cin; 5 using std::cout; 6 using std::endl; 7 using std::string; 8 9 const int SIZE = 26; 10 const char B

bzoj4103异或运算 可持久化trie树

要去清华冬令营了,没找到2016年的题,就先坐一坐15年的. 因为n很小,就按照b串建可持久化trie树,a串暴力枚举. 其他的直接看代码. #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; inline int read() { int x=0,f=1,ch=getchar(); while(ch<'0'||ch

hihoCoder 1014 Trie树

#1014 : Trie树 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进. 这一天,他们遇到了一本词典,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能对于每一个我给出的字符串,都在这个词典里面找到以这个字符串开头的所有单词呢?” 身经百战的小Ho答道:“怎么会不能呢!你每给我一个字符串,我就依次遍历词典里的所有单词,检查你给我的字

HDU 1251 Trie树模板题

1.HDU 1251 统计难题  Trie树模板题,或者map 2.总结:用C++过了,G++就爆内存.. 题意:查找给定前缀的单词数量. #include<iostream> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #include<cstdio> #define max(a,b) a>b?a:b #define F(i,a,b

hiho一下 第二周&amp;第四周:从Trie树到Trie图

hihocoder #1014 题目地址:http://hihocoder.com/problemset/problem/1014 hihocoder #1036 题目地址: http://hihocoder.com/problemset/problem/1036 trie图其实就是trie树+KMP #1014trie树 #include<stdio.h> #include <algorithm> #include <cstring> #include <str

剑指Offer——Trie树(字典树)

剑指Offer--Trie树(字典树) Trie树 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高. Trie的核心思想是空间换时间.利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的. Trie树也有它的缺点,Trie树的内存消耗非常大.当然,或许用左儿子右兄弟的方法建树的话,可能会好点.可见,优