一、 序言
上一篇文章中,给出了 trie 树的一个实现。可以看到,trie 树有一个巨大的弊病,内存占用过大。
本文给出另一种数据结构来解决上述问题---- Ternary Search Tree (三叉树)
二、数据结构定义
Trie 树中每个节点包含了 26 个指针,但有很大一部分的指针是 NULL 指针,因此浪费了大量的资源。
一种改进措施就是,以一棵树来代替上述的指针数组。
节点定义如下:
struct Node
{
char data;
Node *left, *center, *right;
bool isStr;
Node(char x, bool flag)
: data(x), left(NULL), right(NULL), center(NULL), isStr(flag) {}
};
一个节点代表了一个字母,左孩子的字母小于当前节点,右孩子的字母大于当前节点。
同时每个节点包含一个标记:指出当前节点是否是单词的结尾。
如下图:
这个图很容易理解错。我详细讲解以下。
首先,根节点是 A, 以 A 为开头的单词都在 中子树中;
左子树表示那些首字母 < A 的单词集合;
中子树表示那些首字母 = A 的单词集合;
右子树表示那些首字母 > A 的单词集合;
黄色表示单词的结尾;
下图中包含以下单词: AB ABCD ABBA BCD
三、与 Trie 树的比较
当建立一个 7000+ 的词典时,
1. Trie 树共消耗了大约 22383 * 27 * 4 BYTE = 2.4 M
2. Ternary Tree 共消耗了 22468 * 14 BYTE = 0.31M
可以看出,在内存占用方面 Ternary Tree 较 Trie 树有着巨大的优势。
四、代码
// Last Update:2014-04-29 22:47:20
/**
* @file ternary-tree.h
* @brief ternary tree
* @author shoulinjun@126.com
* @version 0.1.00
* @date 2014-04-22
*/
#ifndef TERNARY-TREE_H
#define TERNARY-TREE_H
#include
#include
#include
using std::string;
using std::cout;
using std::endl;
struct Node
{
char data;
Node *left, *center, *right;
bool isStr;
Node(char x, bool flag)
: data(x), left(NULL), right(NULL), center(NULL), isStr(flag) {}
};
class TernaryTree
{
public:
TernaryTree(): root(NULL){}
~TernaryTree() { destroy_tree(root);
cout << "count: " << count << endl;}
void Add(const string &str);
bool Search(const string &str) const;
// 打印所以以str开头的单词
void Dfs(const string &str);
void Input(const string &file);
private:
//not copyable
TernaryTree(const TernaryTree &);
TernaryTree& operator=(const TernaryTree&);
Node *root;
static size_t count;
void add(const string &str, size_t pos, Node* &root);
bool search(const string &str, size_t pos, Node *root) const;
void dfs(Node *p, string &path);
void destroy_tree(Node* &root);
};
size_t TernaryTree::count = 0;
void TernaryTree::Add(const string &str)
{
add(str, 0, root);
}
bool TernaryTree::Search(const string &str) const
{
return search(str, 0, root);
}
void TernaryTree::add(const string &str, size_t pos, Node* &root)
{
if(root == NULL){
root = new Node(str[pos], false);
++ count;
}
if(str[pos] < root->data){
add(str, pos, root->left);
}
else if(str[pos] > root->data){
add(str, pos, root->right);
}
else{
if(pos + 1 == str.size()){
root->isStr = true;
return;
}
add(str, pos+1, root->center);
}
}
bool TernaryTree::search(const string &str, size_t pos, Node *root) const
{
if(str.empty() || root == NULL) return false;
if(str[pos] < root->data){
return search(str, pos, root->left);
}
else if(str[pos] > root->data){
return search(str, pos, root->right);
}
else{
if(pos + 1 == str.size())
return root->isStr;
return search(str, pos+1, root->center);
}
}
void TernaryTree::Dfs(const string &str)
{
Node *p(root);
int cur(0);
while(p && cur != str.size())
{
if(p->data == str[cur]){
p = p->center;
++ cur;
}
else if(p->data > str[cur]){
p = p->left;
}
else{
p = p->right;
}
}
string path = str;
// find the root node
dfs(p, path);
}
void TernaryTree::dfs(Node *p, string &path)
{
if(p == NULL) return;
// center sub-tree
path.push_back(p->data);
if(p->isStr) cout << path << endl;
dfs(p->center, path);
path.resize(path.size()-1);
// left sub-tree
dfs(p->left, path);
// right sub-tree
dfs(p->right, path);
}
void TernaryTree::destroy_tree(Node* &root)
{
if(root == NULL) return;
destroy_tree(root->left);
destroy_tree(root->center);
destroy_tree(root->right);
delete root;
root = NULL;
}
void TernaryTree::Input(const string &str)
{
std::ifstream ifile(str.c_str());
string word;
while(ifile >> word)
{
Add(word);
}
ifile.close();
}
#endif /*TERNARY-TREE_H*/