trie树是一个字典,可以容纳大量的字符串,可以快速查找某个特定的字符串或它的前缀。
trie树以空间换时间,但空间上的浪费还是很明显的,必要时可以考虑优化trie树,如使用双数组的trie
树,以节省空间。
trie树的优势的基础是数组的a[i]优势。
trie树查找一个字符串的最差的时间复杂度是O(h),h是树的高度,这时未必就比二分法好,更没有B树快
。
trie树有一个基本数组,数组的每一个元素指向一个结点。结点也是一个数组,数组的每个元素也指向一
个结点......
基本数组跟结点数组初始化时各个元素置为空。创建trie树时,对加进来的字符串进行逐个字符扫描;每
个字符对应一个hash值,根据这个hash值迅速定位到一个数组元素,如果这个元素为空就创建一个结点数
组,并指定它的前缀等然后在这个结点上定位下一个字符;如果元素不为空则在这个元素指向的结点数组
上定位下一个字符。如此递归,直至所有字符都定位完为止。
查找一个字符串时,也是逐个字符进行。根据第一个字符的hash值快速在基本数组上定位,然后根据下一
个字符在结点数组上定位,直到所有字符查找完毕或找到结点的结束标志或数组元素为空为止。
以下的例子“抄袭”了网上的一些代码。
这里的trie树是一般的trie树,只考虑一次性的静态创建,然后只用于查找。只用于Ascii字符。
vc++8.0 console application
/ trie.h
#pragma once
namespace bluesky_base_adt
{
const int TOP_ARRAY_MAX_SIZE = 256;
class Node
{
public:
Node( int nLevel );
~Node( );
public:
char m_chPreCharactor; // 前缀
bool m_bIsEnd; // 是否该词已经终止
Node** m_pSubNode; // 存放子结点指针的数组
};
class Trie
{
public:
Trie( );
~Trie( );
public:
void AddOneString( char* str );
int FindOneString( char* str );
void DeleteTrieTree( );
private:
void Add( Node * & pNode, char* str, int nLevel );
int Find( Node* pNode, char* str, int nLevel );
int GetHashValue( char str, int nLen );
void DeleteTrie( Node* pNode, int nLevel );
protected:
private:
Node* m_pRootNodes[TOP_ARRAY_MAX_SIZE];
};
}
trie.cpp
#include "trie.h"
using namespace bluesky_base_adt;
// 计算子结点数组的长度
inline int SubNodeLength( int nLevel )
{
// return nLevel > 5 ? 1 : 256 / ( nLevel * nLevel * nLevel );
return TOP_ARRAY_MAX_SIZE;
}
Node::Node(int nLevel ) : m_bIsEnd( false ), m_chPreCharactor('/0')
{
int nSubNodeLen = SubNodeLength( nLevel );
m_pSubNode = new Node* [nSubNodeLen ];
for ( int nPos = 0; nPos < nSubNodeLen; nPos ++ )
{
m_pSubNode[nPos] = NULL;
}
}
Node::~Node( )
{
// add code here
delete m_pSubNode;
}
/
Trie::Trie()
{
for ( int nPos = 0; nPos < TOP_ARRAY_MAX_SIZE; nPos ++ )
{
m_pRootNodes[nPos] = NULL;
}
}
Trie::~Trie( )
{
// add code here
}
/************************************************************************/
/* 内部函数
/************************************************************************/
// add one string
void Trie::Add( Node * & pNode, char* str, int nLevel )
{
Node* pCurrentNode = NULL;
if ( pNode == NULL )
{
pNode = new Node( nLevel + 1 );
pNode -> m_chPreCharactor = *str;
}
pCurrentNode = pNode;
if ( *(str + 1) == '/0' )
{
pCurrentNode->m_bIsEnd = true;
return;
}
int hashValue = GetHashValue( *(str + 1), SubNodeLength( nLevel + 1 ) );
Add( pCurrentNode->m_pSubNode[hashValue], str + 1, nLevel + 1 );
return;
}
// find one string
int Trie::Find( Node* pNode, char* str, int nLevel )
{
if ( pNode == NULL || *str == '/0' )
return 0;
if ( pNode->m_chPreCharactor == *str )
{
if ( pNode->m_bIsEnd && *(str+1) == '/0' )
return nLevel;
int nHashValue = GetHashValue( *(str + 1), SubNodeLength( nLevel + 1 ) );
return Find( pNode->m_pSubNode[nHashValue], str + 1, nLevel + 1 );
}
return 0;
}
// get hash value; each charactor has one hash_value different from others
int Trie::GetHashValue( char str, int nLen )
{
return int(str) % nLen;
}
// delete the trie tree
void Trie::DeleteTrie( Node* pNode, int nLevel )
{
if ( pNode == NULL )
return;
int nNodeLen = SubNodeLength( nLevel + 1 );
for ( int nPos = 0; nPos < nNodeLen; nPos ++ )
{
DeleteTrie( pNode->m_pSubNode[nPos], nLevel+1 );
}
delete pNode;
}
/************************************************************************/
/* 对外接口
/************************************************************************/
// add one string to the Trie_Tree
void Trie::AddOneString( char* str )
{
int nHashValue = GetHashValue( *str, TOP_ARRAY_MAX_SIZE );
Add( m_pRootNodes[nHashValue], str, 1 );
}
// Find one string from the Trie_Tree
int Trie::FindOneString( char* str )
{
int nHashValue = GetHashValue( *str, TOP_ARRAY_MAX_SIZE );
return Find( m_pRootNodes[nHashValue], str, 1 );
}
// delete the Trie_Tree
void Trie::DeleteTrieTree()
{
for ( int nPos = 0; nPos < TOP_ARRAY_MAX_SIZE; nPos ++ )
{
if ( m_pRootNodes[nPos] != NULL )
{
DeleteTrie( m_pRootNodes[nPos], 1 );
}
}
}
/// test code
#include <iostream>
using namespace std;
#include "trie.h"
using namespace bluesky_base_adt;
const int MAX_LEN = 560;
// test trie
void test_trie( )
{
Trie trieTest;
trieTest.AddOneString( "apple" );
trieTest.AddOneString( "appler" );
trieTest.AddOneString( "at" );
trieTest.AddOneString( "basket ball" );
trieTest.AddOneString( "bell" );
trieTest.AddOneString( "God" );
trieTest.AddOneString( "123" );
int nFindCode = trieTest.FindOneString( "apple" );
if ( nFindCode == 0 )
cout << "not found" << endl;
else
cout << "got it, pos: " << nFindCode << endl;
trieTest.DeleteTrieTree();
cout << "******** end *******" << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
//for ( int nPos = 0; nPos < 100000; nPos ++ ) // no mem leak
test_trie( );
return 0;
}