Created
June 4, 2012 15:54
-
-
Save paratechnical/2869170 to your computer and use it in GitHub Desktop.
C# radix tree
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Tree | |
{ | |
/// <summary> | |
/// store the tree's root | |
/// </summary> | |
private Node _root; | |
/// <summary> | |
/// construct a new tree with it's root | |
/// </summary> | |
public Tree() | |
{ | |
_root = new Node(""); | |
} | |
/// <summary> | |
/// insert a word into the tree | |
/// </summary> | |
/// <param name="word"></param> | |
public void Insert(string word) | |
{ | |
InsertRec(word, _root); | |
} | |
/// <summary> | |
/// recursively traverse the tree | |
/// carry the word you want inserted until a proper place for it is found and it can be inserted there | |
/// if a node already stores a substring of the word(substrnig with the same first letter as the word itself) | |
/// then that substring must be removed from the word and it's children checked out next | |
/// hence the name wordPart - part of a word | |
/// </summary> | |
/// <param name="wordPart">the part of the word that is to be inserted that is not already included in any of the tree's nodes</param> | |
/// <param name="curNode">the node currently traversed</param> | |
private void InsertRec(string wordPart, Node curNode) | |
{ | |
//get the number of characters that the word part that is to be inserted and the current node's label have | |
//in common starting from the first position of both strings | |
//matching characters in the two strings = have the same value at the same position in both strings | |
var matches = MatchingConsecutiveCharacters(wordPart, curNode); | |
//if we are at the root node | |
//OR | |
//the number of characters from the two strings that match is | |
//bigger than 0 | |
//smaller than the the part of the word that is to be inserted | |
//bigger than the the label of the current node | |
if ((matches == 0) || (curNode == _root) || | |
((matches > 0) && (matches < wordPart.Length) && (matches >= curNode.Label.Length))) | |
{ | |
//remove the current node's label from the word part | |
bool inserted = false; | |
var newWordPart = wordPart.Substring(matches, wordPart.Length - matches); | |
//search the node's subnodes and if the subnode label's first character matches | |
//the word part's first character then insert the word part after this node(call the | |
//current method recursively) | |
foreach(var child in curNode.SubNodes) | |
if (child.Label.StartsWith(newWordPart[0].ToString())) | |
{ | |
inserted = true; | |
InsertRec(newWordPart, child); | |
} | |
if (inserted == false) | |
{ | |
curNode.SubNodes.Add(new Node(newWordPart)); | |
} | |
} | |
else if(matches < wordPart.Length) | |
{ | |
//in this case we have to nodes that we must add to the tree | |
//one is the node that has a label extracted from the current node's label without the string of | |
//matching characters(common characters) | |
//the other is the node that has it's label extracted from the current word part minus the string | |
//of matching characters | |
string commonRoot = wordPart.Substring(0, matches); | |
string branchPreviousLabel = curNode.Label.Substring(matches, curNode.Label.Length - matches); | |
string branchNewLabel = wordPart.Substring(matches, wordPart.Length - matches); | |
curNode.Label = commonRoot; | |
var newNodePreviousLabel = new Node(branchPreviousLabel); | |
newNodePreviousLabel.SubNodes.AddRange(curNode.SubNodes); | |
curNode.SubNodes.Clear(); | |
curNode.SubNodes.Add(newNodePreviousLabel); | |
var newNodeNewLabel = new Node(branchNewLabel); | |
curNode.SubNodes.Add(newNodeNewLabel); | |
} | |
else if (matches == curNode.Label.Length) | |
{ | |
//in this case we don't do anything because the word is already added | |
} | |
else if (matches > curNode.Label.Length) | |
{ | |
//add the current word part minus the common characters after the current node | |
string newNodeLabel = curNode.Label.Substring(curNode.Label.Length, wordPart.Length); | |
var newNode = new Node(newNodeLabel); | |
curNode.SubNodes.Add(newNode); | |
} | |
} | |
/// <summary> | |
/// given a string and a node the number of characters that the string and the node's label have | |
/// in common starting from the first character in each is returned | |
/// </summary> | |
/// <param name="word">a string that is to be compared with the node's label</param> | |
/// <param name="curNode">a node</param> | |
/// <returns></returns> | |
private int MatchingConsecutiveCharacters(string word, Node curNode) | |
{ | |
int matches = 0; | |
int minLength = 0; | |
//see which string is smaller and save it's lenght | |
//when cycling throught the two strings we won't go any further than that | |
if (curNode.Label.Length >= word.Length) | |
minLength = word.Length; | |
else if (curNode.Label.Length < word.Length) | |
minLength = curNode.Label.Length; | |
if(minLength > 0) | |
//go throught the two streams | |
for (int i = 0; i < minLength; i++) | |
{ | |
//if two characters at the same position have the same value we have one more match | |
if(word[i] == curNode.Label[i]) | |
matches++; | |
else | |
//if at any position the two strings have different characters break the cycle | |
break; | |
} | |
//and return the current number of matches | |
return matches; | |
} | |
public bool Lookup(string word) | |
{ | |
return LookupRec(word, _root); | |
} | |
/// <summary> | |
/// look for a word in the tree begining at the current node | |
/// </summary> | |
/// <param name="wordPart"></param> | |
/// <param name="curNode"></param> | |
/// <returns></returns> | |
private bool LookupRec(string wordPart, Node curNode) | |
{ | |
var matches = MatchingConsecutiveCharacters(wordPart, curNode); | |
if ((matches == 0) || (curNode == _root) || | |
((matches > 0) && (matches < wordPart.Length) && (matches >= curNode.Label.Length))) | |
{ | |
var newLabel = wordPart.Substring(matches, wordPart.Length - matches); | |
foreach (var child in curNode.SubNodes) | |
if (child.Label.StartsWith(newLabel[0].ToString())) | |
return LookupRec(newLabel, child); | |
return false; | |
} | |
else if (matches == curNode.Label.Length) | |
{ | |
return true; | |
} | |
else return false; | |
} | |
//Find successor: Locates the smallest string greater than a given string, by lexicographic order. | |
public string FindSuccessor(string word) | |
{ | |
return FindSuccessorRec(word, _root,string.Empty); | |
} | |
private string FindSuccessorRec(string wordPart, Node curNode,string carry) | |
{ | |
var matches = MatchingConsecutiveCharacters(wordPart, curNode); | |
if ((matches == 0) || (curNode == _root) || | |
((matches > 0) && (matches < wordPart.Length) )) | |
{ | |
var newLabel = wordPart.Substring(matches, wordPart.Length - matches); | |
foreach (var child in curNode.SubNodes) | |
if (child.Label.StartsWith(newLabel[0].ToString())) | |
return FindSuccessorRec(newLabel, child, carry + curNode.Label); | |
return curNode.Label; | |
} | |
else if (matches < curNode.Label.Length) | |
{ | |
return carry + curNode.Label; | |
} | |
else if (matches == curNode.Label.Length) | |
{ | |
carry = carry + curNode.Label; | |
int min = int.MaxValue; | |
int index = -1; | |
for (int i = 0; i < curNode.SubNodes.Count;i++ ) | |
if (curNode.SubNodes[i].Label.Length < min) | |
{ | |
min = curNode.SubNodes[i].Label.Length; | |
index = i; | |
} | |
if (index > -1) | |
return carry + curNode.SubNodes[index].Label; | |
else | |
return carry; | |
} | |
else return string.Empty; | |
} | |
/// <summary> | |
///Find predecessor: Locates the largest string less than a given string, by lexicographic order. | |
/// </summary> | |
/// <param name="?"></param> | |
/// <returns></returns> | |
public string FindPredecessor(string word) | |
{ | |
return FindPredecessorRec(word, _root,string.Empty); | |
} | |
/// <summary> | |
/// </summary> | |
/// <param name="wordPart"></param> | |
/// <param name="curNode"></param> | |
/// <returns></returns> | |
private string FindPredecessorRec(string wordPart, Node curNode,string carry) | |
{ | |
var matches = MatchingConsecutiveCharacters(wordPart, curNode); | |
if ((matches == 0) || (curNode == _root) || | |
((matches > 0) && (matches < wordPart.Length) && (matches >= curNode.Label.Length))) | |
{ | |
var newLabel = wordPart.Substring(matches, wordPart.Length - matches); | |
foreach (var child in curNode.SubNodes) | |
if (child.Label.StartsWith(newLabel[0].ToString())) | |
return FindPredecessorRec(newLabel, child, carry + curNode.Label); | |
return carry + curNode.Label; | |
} | |
else if (matches == curNode.Label.Length) | |
{ | |
return carry + curNode.Label; | |
} | |
else return string.Empty; | |
} | |
/// <summary> | |
/// Delete: Delete a string from the tree. First, we delete the corresponding leaf. | |
/// Then, if its parent only has one child remaining, we delete the parent and merge the two incident edges. | |
/// </summary> | |
/// <param name="label"></param> | |
public void Delete(string label) | |
{ | |
DeleteRec(label, _root); | |
} | |
/// <summary> | |
/// delete a word from the tree means delete the last leaf that makes up the stored word | |
/// </summary> | |
/// <param name="label"></param> | |
/// <param name="curNode"></param> | |
public void DeleteRec(string wordPart, Node curNode) | |
{ | |
var matches = MatchingConsecutiveCharacters(wordPart, curNode); | |
if ((matches == 0) || (curNode == _root) || | |
((matches > 0) && (matches < wordPart.Length) && (matches >= curNode.Label.Length))) | |
{ | |
var newLabel = wordPart.Substring(matches, wordPart.Length - matches); | |
foreach (var child in curNode.SubNodes) | |
if (child.Label.StartsWith(newLabel[0].ToString())) | |
{ | |
if (newLabel == child.Label) | |
{ | |
if (child.SubNodes.Count == 0) | |
{ | |
curNode.SubNodes.Remove(child); | |
return; | |
} | |
} | |
DeleteRec(newLabel, child); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment