近期实现的一个数据结构,当做学习C++ 的小练习吧,零基础真的很痛苦。
(介绍内容为摘抄http://blog.csdn.net/nash_/article/details/8227610)
Trie树(又叫字典树,前缀树,单词查找树,键树)是一种树形数据结构
Trie树的特点:根节点为空值,剩下每一个节点保存一个字母。知道这些就够了!
我们再来看看这棵树能干什么?如果从根节点遍历到某一个节点把路径节点的值连在一起就构成了一个字符串,利用这个特点很容易想到这棵树的第一个功能能帮我们查找某一个单词是否在树中(需要在每一个节点设置一个标志,表示从根节点到此节点是否构成一个单词);如果该单词存在,我们可以利用它实现第二个功能:去除重复单词;同样如果该词存,在我们还可以看出它的第三个功能:统计单词频率;因为这是一个树形结构我们利用这个特点很容易看出它的第四个功能能帮我们查找N个单词的最长公共前缀;如果我们按顺序遍历输出整棵树,发现它的第五个功能:对字符串排序。
这棵树创建看起来比较容易,就有一个问题需要我们考虑:父节点如何保存孩子节点? 主要有两种方式供大家参考:
1.因为是英文字符,我们可以用Node[26]来保存孩子节点(如果是数字我们可以用Node[10]),这种方式最快,但是并不是所有节点都会有很多孩子,所以这种方式浪费的空间太多
2.用一个链表根据需要动态添加节点。这样我们就可以省下不小的空间,但是缺点是搜索的时候需要遍历这个链表,增加了时间复杂度。
下面我用数组保存孩子节点的方式实现的trie树:
首先是Trie.h头文件:
/** Date:2013.12.22 Author:DJ Description: Trie.cpp的头文件,对Tire_Node结构体和Tire类进行声明 */ #ifndef TRIE_H #define TRIE_H #include <iostream> using namespace std; const int branchNum = 26;//共有26个英文字母,每个结点最多有26个分支 /** 定义一个Trie结点 */ struct Trie_Node { bool isWord;//如果值为true,则代表当前节点为一个单词(字符序列)的结尾 int n_Branch;//当前包含分支数 Trie_Node *branch[branchNum];//结点指针数组,数组中的每个元素为一个指针,下标代表相应字母,如branch[0]对应a Trie_Node() { this->isWord = false; this->n_Branch = 0; for(int i = 0; i < branchNum; ++i) { branch[i] = NULL; } } ~Trie_Node() { cout << "~Trie_Node()" << endl; } }; class Trie { public: Trie() { root = new Trie_Node(); } ~Trie(){} bool Insert(const char* word); bool Search(const char* word); bool Delete(char* word); string ShowTrie(Trie_Node* node); public: Trie_Node *root; }; #endif // TRIE_H
#include "Trie.h" /** \brief 插入一个单词 * 从根结点开始,查看结点分支数组中,当前字符对应的元素是否为空,如果为空,则表示没有这个分支,需要新建 如果不为空,则已经存在,继续向下插入下一个字符 * \param 待插入单词 * \return 如果插入成功,返回true * */ bool Trie::Insert(const char* word) { Trie_Node *node = root; while(*word)//遍历字符串的每一个字符 { int location = *word - 'a'; if(node->branch[location] != NULL)//该字符已经存在 { node = node->branch[location]; } else { Trie_Node *temp = new Trie_Node(); node->branch[location] = temp; node->n_Branch++;//分支数加1 node = temp; } word++; } if(node->isWord == true)//插入到最后一个字符,是一个单词的结尾,则整个单词已经存在,插入失败 { return false; } else { node->isWord = true; return true; } } /** \brief 搜索一个单词 * 从根结点开始遍历,一次搜索当前字符对应的分支数组元素,如果遇到空,则单词不存在 * \param 待搜索单词 * \return 如果查找到,返回true * */ bool Trie::Search(const char* word) { Trie_Node *node = root; while(*word) { int location = *word - 'a'; if(node->branch[location] == NULL) { return false; } else { node = node->branch[location]; word++; } } if(node->isWord == true) { return true; } return false; } /** \brief 删除一个单词 * * \param 待删除单词 * \param * \return 如果删除删除成功,则返回true * */ bool Trie::Delete(char* word) { Trie_Node *node = root; Trie_Node *deleteNode = root;//记录删除结点,可能是最后一个分叉,也可能是最后一个分叉后最后一个isword为true的结点 char* deleteInWord = word;//记录删除点在word中的位置,这样,该点后面的字符都会从trie中删除 while(*word) { int location = *word - 'a'; if(node->branch[location] != NULL) { if(node->n_Branch >=2 && node->branch[location]->n_Branch <= 1)//查找最后一个分叉 { deleteNode = node; deleteInWord = word; } if(node->isWord == true)//路径中的标记为单词结尾的结点,如果这个结点在最后一个分叉之后,那么删除时, //只需要删除该结点的后续结点即可 { deleteNode = node; deleteInWord = word; } node = node->branch[location]; word++; } else//不存在 { return false; } } if(node->isWord == true)//找到了 { if(node->n_Branch != 0)//不是叶子结点,去除标识符 { node->isWord = false; node->n_Branch--; } else { /**< 保留待删除子字符串的开始结点,即最后一个分叉或者最后一个分叉后面最后一个单词结尾结点 的下一个结点 ,从该结点开始(包括该结点),都将被删除*/ int deleteLocation = *deleteInWord - 'a'; Trie_Node *nextNode = deleteNode->branch[deleteLocation]; /**< 被删除子字符串的父节点所做的操作 */ deleteNode->branch[deleteLocation] = NULL; deleteNode->n_Branch--; while(*deleteInWord)//删除剩余结点 { deleteNode = nextNode; deleteInWord++; if(*deleteInWord != '\0')//如果没有走到字符的最后一个 { deleteLocation = *deleteInWord - 'a'; nextNode = deleteNode->branch[deleteLocation]; } delete deleteNode; } //或者可以倒序删除,不过需要在结点中记录指向父节点的指针 } return true; } return false; } /** \brief 显示Trie树 * 使用递归的方式 * \param 待打印的Trie_Node * \param * \return * */ string Trie::ShowTrie(Trie_Node* node) { string str = ""; int tempBranch = node->n_Branch; if(tempBranch == 0) { return str; } else { str += "-("; for(int i = 0; i < branchNum ; ++i) { if(node->branch[i]!=NULL) { str += 'a' + i; if(node->branch[i]->isWord == true)//单词结尾结点,作注释 { str += "*"; } str += ShowTrie(node->branch[i]) + ","; } } str = str.substr(0,str.length()-1); str += ")"; } }
#include "Trie.h" int main() { Trie *trie = new Trie(); cout << "--------------------------------------" << endl; cout << trie->Insert("a") << endl; cout << trie->Insert("bbc") << endl; cout << trie->Insert("bb") << endl; cout << trie->Insert("abc") << endl; cout << trie->Insert("aber") << endl; cout << trie->ShowTrie(trie->root) << endl; cout << trie->Search("bb") << endl; cout << trie->Delete("bbc") << endl; cout << trie->Search("bb") << endl; cout << trie->Insert("bb") << endl; cout << trie->Insert("aberxy") << endl; cout << trie->ShowTrie(trie->root) << endl; cout << "--------------------------------------" << endl; cout << trie->Delete("aberxy") << endl; cout << trie->ShowTrie(trie->root) << endl; cout << "--------------------------------------" << endl; return 0; }