To implement the kind of storage which stores strings as the search keys , there is a need to have special data structures which can store the strings efficiently and the searching of data based on the string keys is easier, efficient and faster. One such data structure is a tree based implementation called Trie.
Trie is a data structure which can be used to implement a dictionary kind of application. It provides all the functionality to insert a string, search a string and delete a string from the dictionary. The insertion and deletion operation takes O(n) time where n is the length of the string to be deleted or inserted.
Here is a diagrammatical view of a trie nodes I have used in this implementation. The field key is not represented in the diagram due to symmetry purposes.
Let us consider an example to understand tries in detail.
Suppose we have to implement a database for the HR department of an organisation in which we have to store an employee's name and their ages. There is an assumption for this example that there each employee's name is unique.So there is a strange policy in this organisation that any new employee which has a name that already exists in the organisation, it would not hire that new employee.
Let's use this hypothetical example just to understand how tries work.
This is the complete Trie with all the entries. Now let us try deleting the names. I am not capturing the trivial cases.
There is also a video from IIT Delhi which explains the tries. Tries Explained.
The implementation for this Trie is given below. Please provide your suggestions to further improve the implementation.
Trie is a data structure which can be used to implement a dictionary kind of application. It provides all the functionality to insert a string, search a string and delete a string from the dictionary. The insertion and deletion operation takes O(n) time where n is the length of the string to be deleted or inserted.
Some of the application of tries involve web based search engines, URL completion in autocomplete feature, Spell checker etc.
Structure of Trie(Specific to this implementation):
The trie implemented here consists of nodes. Each node has these fields:- Key - Part of the string to be serached,inserted or deleted.
- Value - The value associated with a string (e.g In a dictionary it could be the meaning of the word which we are searching)
- Neighbour node address - It consists of the address of the neighbouring node at the same level.
- Previous neighbour address - It consists of the address of the previous node at the same level.
- Children node address - It consists of the address of the child nodes of the current node.
- Parent node address - It consists of the address of the parent node of the current node.
Here is a diagrammatical view of a trie nodes I have used in this implementation. The field key is not represented in the diagram due to symmetry purposes.
Suppose we have to implement a database for the HR department of an organisation in which we have to store an employee's name and their ages. There is an assumption for this example that there each employee's name is unique.So there is a strange policy in this organisation that any new employee which has a name that already exists in the organisation, it would not hire that new employee.
Let's use this hypothetical example just to understand how tries work.
- Consider we have a new employee named Andrew with age 36. Lets populate our trie for "andrew".
- Now add "tina".
- Add "argo".
- Add "tim".
- Add "t".
- Add "amy".
- Add "aramis".
This is the complete Trie with all the entries. Now let us try deleting the names. I am not capturing the trivial cases.
- Lets try deleting Argo.
- Delete Tina
- Delete Andrew
There is also a video from IIT Delhi which explains the tries. Tries Explained.
The implementation for this Trie is given below. Please provide your suggestions to further improve the implementation.
/*trie.h*/
typedef int trieVal_t;
typedef struct trieNode {
char key;
trieVal_t value;
struct trieNode *next;
struct trieNode *prev;
struct trieNode *children;
struct trieNode *parent;
} trieNode_t;
void TrieCreate(trieNode_t **root);
trieNode_t* TrieSearch(trieNode_t *root, const char *key);
void TrieAdd(trieNode_t **root, char *key, int data);
void TrieRemove(trieNode_t **root, char *key);
void TrieDestroy( trieNode_t* root );
/*trie.c*/
#include <stdio.h>
#include "trie.h"
#include <stdlib.h>
trieNode_t *TrieCreateNode(char key, int data);
void TrieCreate(trieNode_t **root)
{
*root = TrieCreateNode('\0', 0xffffffff);
}
trieNode_t *TrieCreateNode(char key, int data)
{
trieNode_t *node = NULL;
node = (trieNode_t *)malloc(sizeof(trieNode_t));
if(NULL == node)
{
printf("Malloc failed\n");
return node;
}
node->key = key;
node->next = NULL;
node->children = NULL;
node->value = data;
node->parent= NULL;
node->prev= NULL;
return node;
}
void TrieAdd(trieNode_t **root, char *key, int data)
{
trieNode_t *pTrav = NULL;
if(NULL == *root)
{
printf("NULL tree\n");
return;
}
#ifdef DEBUG
printf("\nInserting key %s: \n",key);
#endif
pTrav = (*root)->children;
if(pTrav == NULL)
{
/*First Node*/
for(pTrav = *root; *key; pTrav = pTrav->children)
{
pTrav->children = TrieCreateNode(*key, 0xffffffff);
pTrav->children->parent = pTrav;
#ifdef DEBUG
printf("Inserting: [%c]\n",pTrav->children->key);
#endif
key++;
}
pTrav->children = TrieCreateNode('\0', data);
pTrav->children->parent = pTrav;
#ifdef DEBUG
printf("Inserting: [%c]\n",pTrav->children->key);
#endif
return;
}
if(TrieSearch(pTrav, key))
{
printf("Duplicate!\n");
return;
}
while(*key != '\0')
{
if(*key == pTrav->key)
{
key++;
#ifdef DEBUG
printf("Traversing child: [%c]\n",pTrav->children->key);
#endif
pTrav = pTrav->children;
}
else
break;
}
while(pTrav->next)
{
if(*key == pTrav->next->key)
{
key++;
TrieAdd(&(pTrav->next), key, data);
return;
}
pTrav = pTrav->next;
}
if(*key)
{
pTrav->next = TrieCreateNode(*key, 0xffffffff);
}
else
{
pTrav->next = TrieCreateNode(*key, data);
}
pTrav->next->parent = pTrav->parent;
pTrav->next->prev = pTrav;
#ifdef DEBUG
printf("Inserting [%c] as neighbour of [%c] \n",pTrav->next->key, pTrav->key);
#endif
if(!(*key))
return;
key++;
for(pTrav = pTrav->next; *key; pTrav = pTrav->children)
{
pTrav->children = TrieCreateNode(*key, 0xffffffff);
pTrav->children->parent = pTrav;
#ifdef DEBUG
printf("Inserting: [%c]\n",pTrav->children->key);
#endif
key++;
}
pTrav->children = TrieCreateNode('\0', data);
pTrav->children->parent = pTrav;
#ifdef DEBUG
printf("Inserting: [%c]\n",pTrav->children->key);
#endif
return;
}
trieNode_t* TrieSearch(trieNode_t *root, const char *key)
{
trieNode_t *level = root;
trieNode_t *pPtr = NULL;
int lvl=0;
while(1)
{
trieNode_t *found = NULL;
trieNode_t *curr;
for (curr = level; curr != NULL; curr = curr->next)
{
if (curr->key == *key)
{
found = curr;
lvl++;
break;
}
}
if (found == NULL)
return NULL;
if (*key == '\0')
{
pPtr = curr;
return pPtr;
}
level = found->children;
key++;
}
}
void TrieRemove(trieNode_t **root, char *key)
{
trieNode_t *tPtr = NULL;
trieNode_t *tmp = NULL;
if(NULL == *root || NULL == key)
return;
tPtr = TrieSearch((*root)->children, key);
if(NULL == tPtr)
{
printf("Key [%s] not found in trie\n", key);
return;
}
#ifdef DEBUG
printf("Deleting key [%s] from trie\n", key);
#endif
while(1)
{
if( tPtr->prev && tPtr->next)
{
tmp = tPtr;
tPtr->next->prev = tPtr->prev;
tPtr->prev->next = tPtr->next;
#ifdef DEBUG
printf("Deleted [%c] \n", tmp->key);
#endif
free(tmp);
break;
}
else if(tPtr->prev && !(tPtr->next))
{
tmp = tPtr;
tPtr->prev->next = NULL;
#ifdef DEBUG
printf("Deleted [%c] \n", tmp->key);
#endif
free(tmp);
break;
}
else if(!(tPtr->prev) && tPtr->next)
{
tmp = tPtr;
tPtr->parent->children = tPtr->next;
#ifdef DEBUG
printf("Deleted [%c] \n", tmp->key);
#endif
free(tmp);
break;
}
else
{
tmp = tPtr;
tPtr = tPtr->parent;
tPtr->children = NULL;
#ifdef DEBUG
printf("Deleted [%c] \n", tmp->key);
#endif
free(tmp);
}
}
#ifdef DEBUG
printf("Deleted key [%s] from trie\n", key);
#endif
}
void TrieDestroy( trieNode_t* root )
{
trieNode_t *tPtr = root;
trieNode_t *tmp = root;
while(tPtr)
{
while(tPtr->children)
tPtr = tPtr->children;
if( tPtr->prev && tPtr->next)
{
tmp = tPtr;
tPtr->next->prev = tPtr->prev;
tPtr->prev->next = tPtr->next;
#ifdef DEBUG
printf("Deleted [%c] \n", tmp->key);
#endif
free(tmp);
}
else if(tPtr->prev && !(tPtr->next))
{
tmp = tPtr;
tPtr->prev->next = NULL;
#ifdef DEBUG
printf("Deleted [%c] \n", tmp->key);
#endif
free(tmp);
}
else if(!(tPtr->prev) && tPtr->next)
{
tmp = tPtr;
tPtr->parent->children = tPtr->next;
tPtr->next->prev = NULL;
tPtr = tPtr->next;
#ifdef DEBUG
printf("Deleted [%c] \n", tmp->key);
#endif
free(tmp);
}
else
{
tmp = tPtr;
if(tPtr->parent == NULL)
{
/*Root*/
free(tmp);
return;
}
tPtr = tPtr->parent;
tPtr->children = NULL;
#ifdef DEBUG
printf("Deleted [%c] \n", tmp->key);
#endif
free(tmp);
}
}
}
/*triedriver.c*/
/*
* To Compile : gcc -o trie trie.c triedriver.c
* To run: ./trie
*/
#include <stdio.h>
#include <stdlib.h>
#include "trie.h"
int main()
{
trieNode_t *root;
printf("Trie Example\n");
/*Create a trie*/
TrieCreate(&root);
TrieAdd(&root, "andrew", 1);
TrieAdd(&root, "tina", 2);
TrieAdd(&root, "argo", 3);
TrieAdd(&root, "timor", 5);
TrieRemove(&root, "tim");
TrieAdd(&root, "tim", 6);
TrieRemove(&root, "tim");
TrieAdd(&root, "ti", 6);
TrieAdd(&root, "amy", 7);
TrieAdd(&root, "aramis", 8);
/*Destroy the trie*/
TrieDestroy(root);
}
In order to print the debug messages, use -DDEBUG while compiling with gcc:gcc -o trie trie.c triedriver.c -DDEBUG










