使用Trie树实现的MAP

来源:互联网 发布:mac机ae cc2014序列号 编辑:程序博客网 时间:2024/06/14 22:08

最近看到一种基于Tire树的map结构,其键值类型为string类型,查找速度很快。文章[1]中分析了这种‘TrieMap’原理,对比了其和std::map,std::unordered_map的查找速度。基于Trie树和文章中提到的TrieMap设计,进行实践,给出自己构造的TrieMap以供参考学习。

Trie树,又称单词查找树、字典树。是一种哈希树的变种,是一种用于快速检索的多叉树结构。Trie树查找的时间复杂度在O(n),实际上这是以消耗更多的内存空间为代价的。以本文构造的Trie树为例,每个节点都包含了128个(因为我们只接受ASCII码在0-127的之间的字符组成的字符串作为键值)子节点。这其实造成了极大的空间浪费,但是带来的好处是:可以使用直接定址的下标访问来判断某个节点是否为空。比如TrieNode *ptr指向的是某个节点,其含有128个子节点,那么对于string str = “hello”;可以直接用ptr->pChild[str[i]]来访问或判断该节点的状态。此外,作为一种树形数据结构,相邻兄弟节点可以存储在一个数组中,但父节点和子节点之间的连接还是通过链表的结构。
本文要讲的TrieMap,其本质不过是在Trie树的节点中增加了一块空间,用于存储对于的Value值,但这也带来了进一步的空间浪费,以test:test123这个键值对为例,前三个节点t->e->s实际上都分配了用于存储Value的空间,但只有t->e->s->t的最后一个节点t存储了对于的键值‘test123’。当插入test:test123后,从空间的角度来看,树的第一层共有128个节点,其中第117个节点t不为空,其余均为空;第二层共有128个节点,其中只有t对应的第二层中e的位置不为空,其余均为空…但是当再次插入新的键值对task:tasker时,第一层t节点位置与键值test共有存储位置,但是第二层会增加128个节点,却仍然只有a对应的存储位置不为空,这也是Trie树的特点,搜索引擎系统用于文本词频统计的原理也基于此。如果键值有公共的前缀,可以节省内存开销。
TrieMap的键值需为字符串类型,或是可以转换成字符串类型,存在一定的局限性。但通过模板类设计的TrieNode节点,可以保证Key对应的Value支持较为宽广的类型。TrieMap对外的接口中,提供了:
(1).用于实例化对象的构造函数;
(2).用于建立TrieMap结构的Insert()函数;
(3).用于查找键值对的函数Find()
(4).用于判断TrieMap是否为空的Empty()函数
其中建立和查找TrieMap的键可为c-style的字符串,以及string类型,实际上代码中只是将string类型统一转换为c-style的字符串。
下面给出实现,并使用CPPUNIT进行测试。

//triemap.h#ifndef _TRIE_MAP_201612#define _TRIE_MAP_201612#include <string.h>#include <string>#include <stdlib.h> using namespace std;//ASCII码0-127,不包括扩展的ASCII码,对应每个节点的叶子节点个数const int NODESIZE = 128;//节点的数据结构template <class T>class TrieNode{public:    TrieNode()    {        isLeaf = false;        ::memset(this->pChild, 0, NODESIZE * sizeof(TrieNode*));    }public:    T value;//节点可以存储的数据类型    bool isLeaf;//标记该节点是否是叶子节点    TrieNode* pChild[NODESIZE];//每个节点的子节点};template<class T>class TrieMap{public:    //constructor    TrieMap();    //deconstructor    ~TrieMap();    //向Tire树中插入新节点    void Insert(const char *key, const T &value);    //向Tire树中插入新节点--string    void Insert(const string &key, const T &value);    //查找    bool Find(const char *key,T &value);    //查找--string    bool Find(const string &key,T &value);    //    bool Empty()const;private:    //创建新的TrieNode节点    TrieNode<T> *newTrieNode();    //用于删除某节点及其所有的子节点    void Delete(TrieNode<T> *&ptr);private:    TrieNode<T> *root;//Trie树的根节点};template<class T>TrieMap<T>::TrieMap(){    this->root = new TrieNode<T>;    if( NULL == this->root )        exit(-1);}template<class T>TrieMap<T>::~TrieMap(){    if(this->root != NULL)        this->Delete(root);}template<class T>void TrieMap<T>::Insert(const char *key, const T &value){    TrieNode<T> *ptr = this->root;    int idx;    int len = strlen(key);    for(int i = 0 ; i <  len ;++i)    {        idx = key[i];        //该子节点不存在        if(ptr->pChild[idx] == NULL)        {            ptr->pChild[idx] = this->newTrieNode();        }        //指向该节点,继续遍历        ptr = ptr->pChild[idx];    }    ptr->isLeaf = true;    ptr->value = value;}template<class T>void TrieMap<T>::Insert(const string &key, const T &value){    this->Insert(key.c_str(),value);}template<class T>bool TrieMap<T>::Find(const char *key, T &value){    TrieNode<T> *ptr = this->root;    int idx;    int len = strlen(key);    int i;    for( i = 0 ; i < len ;++i)    {        idx = key[i];        //输入的字符串,不在普通ASCII码范围        if(idx < 0 || idx >= NODESIZE)            return false;        //节点为空,可能是未找到,也可能是遍历结束了        if(ptr->pChild[idx] == NULL)        {            break;        }        //找到该节点,继续遍历            else        {            ptr = ptr->pChild[idx];        }    }    if(i == len && ptr->isLeaf)    {        value = ptr->value;        return true;    }    return false;}template<class T>bool TrieMap<T>::Find(const string &key,T &value){    return this->Find(key.c_str(), value);}template<class T>bool TrieMap<T>::Empty()const{    bool flag = true;    for(int i = 0 ;i < NODESIZE ;++i)    {        if(this->root->pChild[i] != NULL)        {            flag = false;            return flag;        }           }    return flag;}template<class T>TrieNode<T>* TrieMap<T>::newTrieNode(){    TrieNode<T> *ptr = new TrieNode<T>();    if(NULL == ptr)    {        exit(-1);    }    return ptr;}template<class T>void TrieMap<T>::Delete(TrieNode<T> *&ptr){    if(NULL == ptr)        return;    for(int i = 0 ;i < NODESIZE ;++i)    {        if(NULL != ptr->pChild[i] )        {            this->Delete(ptr->pChild[i]);        }    }    if(NULL != ptr)    {        delete ptr;        ptr = NULL;    }}#endif
//main.cpp#include <iostream>#include "triemap.h"#include <cppunit/extensions/TestFactoryRegistry.h>#include <cppunit/ui/text/TextTestRunner.h>#include <cppunit/extensions/HelperMacros.h>class myTest: public CppUnit::TestFixture {public:    void  checkInsert()    {        triemap.Insert("root","123456");        bool flag = triemap.Empty();        CPPUNIT_ASSERT_MESSAGE("TrieMap not Empty!", (!flag) );    }    void checkFind()     {        triemap.Insert("admin","admin123");        triemap.Insert("tester","tester");        string value;        triemap.Find("admin",value);        int len =  value.length();        CPPUNIT_ASSERT_EQUAL_MESSAGE("Check Find:", len, 8);    }    void checkFindNull()     {        string value;        bool flag = triemap.Find("test",value);        CPPUNIT_ASSERT_MESSAGE("Could't find test!", (!flag) );    }    CPPUNIT_TEST_SUITE( myTest );    CPPUNIT_TEST( checkInsert );    CPPUNIT_TEST( checkFind );    CPPUNIT_TEST( checkFindNull );    CPPUNIT_TEST_SUITE_END();private:    TrieMap<string> triemap;};int main (){    //使用辅助宏注册测试套件    CPPUNIT_TEST_SUITE_REGISTRATION ( myTest );    CppUnit::Test *test =  CppUnit::TestFactoryRegistry::getRegistry().makeTest();    CppUnit::TextTestRunner runner;    runner.addTest(test);    //运行测试,自动显示进度和结果    runner.run("",true);    return 0;}

在示例中,我们插入了键值对tester:tester,但是没有插入键为test的键值对,虽然test为tester的前缀,在Trie树中共用前缀节点,但是由于在TrieNode节点中用布尔变量isLeaf来标记节点的类型,所以即使test为tester的前缀,也无法查找到,这是符合MAP的定义的,也是与Trie树不同之处。
运行结果:
[root@localhost map]# ./run

OK (3 tests)


[1].Trie实践:一种比哈希表更快的数据结构.http://blog.csdn.net/stevenkylelee/article/details/38343985
[2].Trie树:应用于统计和排序.http://blog.csdn.net/hguisu/article/details/8131559

0 0
原创粉丝点击