原理
先看个例子,存储字符串abc、ab、abm、abcde、pm可以利用以下方式存储
上边就是Trie树的基本原理:利用字串的公共前缀来节省存储空间,最大限度的减少无谓的字串比较。
应用
Trie树又称单词查找树,典型的应用是用于统计,排序和保存大量的字符串(不仅用于字符串),所以经常被搜索引擎系统用于文本词频的统计。
设计
trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
结点可以设计成这样:
//trie节点定义 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) { if (children[i] != NULL) { delete children[i]; children[i] = NULL; } } } public: int terminableSize; //存储以此节点为结尾的字符串的个数 int nodeSize; //记录此节点孩子的个数 trieNode *children[Size]; //该数组记录指向孩子的指针 };
图示
树设计成这样:
//trie树定义 template <int Size> class trie { public: trie() : root(new trieNode<Size>) {} int Index(char ch) { //取某个字符在children数组中的位置 return static_cast<int>(ch % Size); } void insert(const string& str); //插入字符串str bool find(const string& str); //查找字符串str bool downNodeAlone(trieNode<Size> *ptr); //判断当前节点往下是否是单一的字符串 bool erase(const string& str); //删除字符串str int sizeAll(const trieNode<Size> *pNode); //统计不重复字符串个数 int sizeNoneRedundant(const trieNode<Size> *pNode); //统计重复字符串个数 public: trieNode<Size> *root; };
index字串索引利用(char % 26) 得到,这样'a' % 26 = 19, 'b' % 26 = 20
实现
插入
以插入abc、ab为例
删除
删除结点,首先查找此字串是否在树中,如果在树中,再查找此结点以下的部分是不是都是只有一个孩子,并且每个结点只有叶子结点是结束结点,如果不是继续往下重复上边过程。
统计字串个数
分两种情况
参考代码
#include <iostream> #include <string> using namespace std; //trie节点定义 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) { if (children[i] != NULL) { delete children[i]; children[i] = NULL; } } } public: int terminableSize; //存储以此节点为结尾的字符串的个数 int nodeSize; //记录此节点孩子的个数 trieNode *children[Size]; //该数组记录指向孩子的指针 }; //trie树定义 template <int Size> class trie { public: trie() : root(new trieNode<Size>) {} int Index(char ch) { //取某个字符在children数组中的位置 return static_cast<int>(ch % Size); } void insert(const string& str); //插入字符串str bool find(const string& str); //查找字符串str bool downNodeAlone(trieNode<Size> *ptr); //判断当前节点往下是否是单一的字符串 bool erase(const string& str); //删除字符串str int sizeAll(const trieNode<Size> *pNode); //统计不重复字符串个数 int sizeNoneRedundant(const trieNode<Size> *pNode); //统计重复字符串个数 public: trieNode<Size> *root; }; template <int Size> void trie<Size>::insert(const string& str) { trieNode<Size> *cur = root; for (size_t i = 0; i < str.size(); ++i) { if (!cur->children[Index(str[i])]) { cur->children[Index(str[i])] = new trieNode<Size>; ++cur->nodeSize; } cur = cur->children[Index(str[i])]; } ++cur->terminableSize; } template <int Size> bool trie<Size>::find(const string& str) { trieNode<Size> *cur = root; for (size_t i = 0; i < str.size(); ++i) { if (!cur->children[Index(str[i])]) { return false; } cur = cur->children[Index(str[i])]; } if (cur->terminableSize > 0) return true; return false; } //判断当前节点往下是否是单一的字符串 template<int Size> bool trie<Size>::downNodeAlone(trieNode<Size> *ptr) { trieNode<Size> *cur = ptr; int terminableSum = 0; while (cur->nodeSize > 0) { terminableSum += cur->terminableSize; if (terminableSum > 1) return false; 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]; break; } } } } if (terminableSum == 1) return true; return false; } //删除字符串str template<int Size> bool trie<Size>::erase(const string& str) { if (find(str)) { trieNode<Size> *cur = root; for (int i = 0; i < str.size(); ++i) { if (downNodeAlone(cur)) { while (i < str.size()) { trieNode<Size> *tmp = cur; cur = cur->children[Index(str[i])]; delete tmp; ++i; } return true; } cur = cur->children[Index(str[i])]; } if (cur->terminableSize > 0) --cur->terminableSize; return true; } return false; } //统计该trie树中包含字符串个数(包括重复字符串) template <int Size> int trie<Size>::sizeAll(const trieNode<Size> *root) { if (root == NULL) return 0; int rev = root->terminableSize; for (int i = 0; i < Size; ++i) { rev += sizeAll(root->children[i]); } return rev; } //统计该trie树中包含字符串个数(不包括重复字符串) template <int Size> int trie<Size>::sizeNoneRedundant(const trieNode<Size> *root) { if (root == NULL) return 0; int rev = 0; if (root->terminableSize > 0) rev = 1; if (root->nodeSize > 0) { for (int i = 0; i < Size; ++i) { rev += sizeNoneRedundant(root->children[i]); } } return rev; } int main() { trie<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 << "sizeNoneRedundant:" << t.sizeNoneRedundant(t.root) << endl; t.erase("h"); cout << "\nSizeALL:" << t.sizeAll(t.root) << endl; cout << "sizeNoneRedundant:" << t.sizeNoneRedundant(t.root) << endl; system("pause"); return 0; }
结果
技术实现细节
1. 对树的删除,并不是树销毁结点,而是通过结点自身的析构函数实现
2. 模版类、模版函数、非类型模版可以参考:http://www.cnblogs.com/kaituorensheng/p/3601495.html
3. 字母的存储并不是存储的字母,而是存储的位置,如果该位置的指针为空,则说明此处没有字母;反之有字母。
4. terminableNum存储以此结点为结束结点的个数,这样可以避免删除时,不知道是否有多个相同字符串的情况。