Existe-t-il des implémentations de trie efficaces en vitesse et en cache en C/C++? Je sais ce qu'est un trie, mais je ne veux pas réinventer la roue, la mettre en œuvre moi-même.
si vous cherchez une implémentation ANSI C, vous pouvez la "voler" à FreeBSD. Le fichier que vous recherchez s'appelle radix.c . Il est utilisé pour gérer le routage des données dans le noyau.
Je me rends compte que la question portait sur des implémentations prêtes, mais pour référence ...
Avant de sauter sur Judy, vous devriez avoir lu " ne comparaison des performances de Judy aux tables de hachage ". Ensuite, googler le titre vous donnera probablement une vie de discussions et de réfutations à lire.
Le trie explicitement conscient du cache que je connais est le trie HAT .
Le HAT-trie, lorsqu'il est correctement implémenté, est très cool. Cependant, pour la recherche de préfixe, vous avez besoin d'une étape de tri sur les compartiments de hachage, ce qui se heurte quelque peu à l'idée d'une structure de préfixe.
Un tri un peu plus simple est le burst-trie qui vous donne essentiellement une interpolation entre un arbre standard quelconque (comme un BST) et un trie. J'aime conceptuellement et c'est beaucoup plus facile à mettre en œuvre.
J'ai eu de la chance avec libTrie . Il n'est peut-être pas spécifiquement optimisé pour le cache, mais les performances ont toujours été décentes pour mes applications.
GCC est livré avec une poignée de structures de données dans le cadre de ses "structures de données basées sur des politiques". Cela comprend quelques implémentations de trois.
http://gcc.gnu.org/onlinedocs/libstdc++/ext/pb_ds/trie_based_containers.html
tableaux Judy : tableaux dynamiques ordonnés très rapides et efficaces en mémoire pour les bits, les entiers et les chaînes. Les tableaux Judy sont plus rapides et plus efficaces en mémoire que n'importe quel arbre de recherche binaire (y compris les arbres avl et rouge-noir).
Les références,
C
)Vous pouvez également essayer TommyDS sur http://tommyds.sourceforge.net/
Voir la page de repères sur le site pour une comparaison de vitesse avec nedtries et judy.
Vous devrez probablement faire des optimisations de cache, car vous devrez adapter les données dans une seule ligne de cache qui est généralement quelque chose comme 64 octets (ce qui fonctionnera probablement si vous commencez à combiner des données, telles que des pointeurs) . Mais c'est délicat :-)
Burst Trie's semble être un peu plus économe en espace. Je ne suis pas sûr de l'efficacité du cache que vous pouvez retirer de n'importe quel index car les caches CPU sont si minuscules. Cependant, ce type de trie est suffisamment compact pour conserver de grands ensembles de données dans RAM (où un Trie normal ne le ferait pas).
J'ai écrit une implémentation Scala d'un triplet rafale qui incorpore également des techniques d'économie d'espace que j'ai trouvées dans l'implémentation de type-forward de GWT.
Partager mon implémentation "rapide" pour Trie, testée uniquement dans le scénario de base:
#define ENG_LET 26
class Trie {
class TrieNode {
public:
TrieNode* sons[ENG_LET];
int strsInBranch;
bool isEndOfStr;
void print() {
cout << "----------------" << endl;
cout << "sons..";
for(int i=0; i<ENG_LET; ++i) {
if(sons[i] != NULL)
cout << " " << (char)('a'+i);
}
cout << endl;
cout << "strsInBranch = " << strsInBranch << endl;
cout << "isEndOfStr = " << isEndOfStr << endl;
for(int i=0; i<ENG_LET; ++i) {
if(sons[i] != NULL)
sons[i]->print();
}
}
TrieNode(bool _isEndOfStr = false):isEndOfStr(_isEndOfStr), strsInBranch(0) {
for(int i=0; i<ENG_LET; ++i) {
sons[i] = NULL;
}
}
~TrieNode() {
for(int i=0; i<ENG_LET; ++i) {
delete sons[i];
sons[i] = NULL;
}
}
};
TrieNode* head;
public:
Trie() { head = new TrieNode();}
~Trie() { delete head; }
void print() {
cout << "Preorder Print : " << endl;
head->print();
}
bool isExists(const string s) {
TrieNode* curr = head;
for(int i=0; i<s.size(); ++i) {
int letIdx = s[i]-'a';
if(curr->sons[letIdx] == NULL) {
return false;
}
curr = curr->sons[letIdx];
}
return curr->isEndOfStr;
}
void addString(const string& s) {
if(isExists(s))
return;
TrieNode* curr = head;
for(int i=0; i<s.size(); ++i) {
int letIdx = s[i]-'a';
if(curr->sons[letIdx] == NULL) {
curr->sons[letIdx] = new TrieNode();
}
++curr->strsInBranch;
curr = curr->sons[letIdx];
}
++curr->strsInBranch;
curr->isEndOfStr = true;
}
void removeString(const string& s) {
if(!isExists(s))
return;
TrieNode* curr = head;
for(int i=0; i<s.size(); ++i) {
int letIdx = s[i]-'a';
if(curr->sons[letIdx] == NULL) {
assert(false);
return; //string not exists, will not reach here
}
if(curr->strsInBranch==1) { //just 1 str that we want remove, remove the whole branch
delete curr;
return;
}
//more than 1 son
--curr->strsInBranch;
curr = curr->sons[letIdx];
}
curr->isEndOfStr = false;
}
void clear() {
for(int i=0; i<ENG_LET; ++i) {
delete head->sons[i];
head->sons[i] = NULL;
}
}
};
J'ai obtenu de très bons résultats (très bon équilibre entre performances et taille) avec marisa-trie par rapport à plusieurs implémentations TRIE mentionnées avec mon jeu de données.