/// <typeparam name="T"></typeparam>
public interface IValueComparer<in T>
/// 传入的 <paramref name="t"/> 就是存放在树里面的数据
/// 采用 <code>x.CompareTo(<paramref name="t"/>)</code> 的方法,注意判断顺序不能相反
/// <param name="t"></param>
/// <returns>如果存根 x 比 <paramref name="t"/> 大,返回 1 等于返回 0 的值</returns>
/// A B-tree implementation.
/// https://github.com/justcoding121/Advanced-Algorithms
public class BTree<T> : IEnumerable<T> where T : IComparable
/// <param name="maxKeysPerNode">默认一个 Node 有多少个数据,默认值是 3 个,注意不能传入小于3个</param>
public BTree(int maxKeysPerNode = 3)
throw new ArgumentException(
$"Max keys per node should be atleast 3. Current value is {maxKeysPerNode}");
_maxKeysPerNode = maxKeysPerNode;
_minKeysPerNode = maxKeysPerNode / 2;
public int Count { get; private set; }
/// Time complexity: O(log(n)).
if (Root == null) return default;
var maxNode = FindMaxNode(Root);
return maxNode.Keys[maxNode.KeyCount - 1];
/// Time complexity: O(log(n)).
if (Root == null) return default;
var minNode = FindMinNode(Root);
public T Find(IValueComparer<T> comparer)
return Find(Root, comparer);
return Find(Root, value);
public T Find(Func<T, int> comparer)
return Find(Root, new DelegateComparer(comparer));
/// Time complexity: O(log(n)).
public void Insert(T newValue)
Root = new BTreeNode<T>(_maxKeysPerNode, null) { Keys = { [0] = newValue } };
var leafToInsert = FindInsertionLeaf(Root, newValue);
InsertAndSplit(ref leafToInsert, newValue, null, null);
/// Time complexity: O(log(n)).
public void Delete(T value)
var node = FindDeletionNode(Root, value);
throw new Exception("Item do not exist in this tree.");
for (var i = 0; i < node.KeyCount; i++)
if (value.CompareTo(node.Keys[i]) != 0)
//if node is leaf and no underflow
//then just remove the node
//replace with max node of left tree
var maxNode = FindMaxNode(node.Children[i]);
node.Keys[i] = maxNode.Keys[maxNode.KeyCount - 1];
RemoveAt(maxNode.Keys, maxNode.KeyCount - 1);
IEnumerator IEnumerable.GetEnumerator()
public IEnumerator<T> GetEnumerator()
return new BTreeEnumerator<T>(Root);
private readonly int _maxKeysPerNode;
private readonly int _minKeysPerNode;
private BTreeNode<T> Root { set; get; }
private static T Find(BTreeNode<T> node, IValueComparer<T> comparer)
//if leaf then its time to insert
for (var i = 0; i < node.KeyCount; i++)
var value = node.Keys[i];
if (comparer.CompareTo(value) == 0)
//if not leaf then drill down to leaf
for (var i = 0; i < node.KeyCount; i++)
var value = node.Keys[i];
if (comparer.CompareTo(value) == 0)
//current value is less than new value
//drill down to left child of current value
if (comparer.CompareTo(value) < 0)
return Find(node.Children[i], comparer);
//current value is grearer than new value
//and current value is last element
if (node.KeyCount == i + 1)
return Find(node.Children[i + 1], comparer);
/// Find the value node under given node.
private static T Find(BTreeNode<T> node, T value)
return Find(node, new DefaultComparer(value));
/// Find the leaf node to start initial insertion
private static BTreeNode<T> FindInsertionLeaf(BTreeNode<T> node, T newValue)
//if leaf then its time to insert
//if not leaf then drill down to leaf
for (var i = 0; i < node.KeyCount; i++)
//current value is less than new value
//drill down to left child of current value
if (newValue.CompareTo(node.Keys[i]) < 0)
return FindInsertionLeaf(node.Children[i], newValue);
//current value is grearer than new value
//and current value is last element
if (node.KeyCount == i + 1)
return FindInsertionLeaf(node.Children[i + 1], newValue);
/// Insert and split recursively up until no split is required
private void InsertAndSplit(ref BTreeNode<T> node, T newValue,
BTreeNode<T> newValueLeft, BTreeNode<T> newValueRight)
//add new item to current node
node = new BTreeNode<T>(_maxKeysPerNode, null);
//newValue have room to fit in this node
//so just insert in right spot in asc order of keys
if (node.KeyCount != _maxKeysPerNode)
InsertToNotFullNode(ref node, newValue, newValueLeft, newValueRight);
//if node is full then split node
//and then insert new median to parent.
//divide the current node values + new Node as left and right sub nodes
var left = new BTreeNode<T>(_maxKeysPerNode, null);
var right = new BTreeNode<T>(_maxKeysPerNode, null);
var currentMedianIndex = node.GetMedianIndex();
//init currentNode under consideration to left
var currentNodeIndex = 0;
//new Median also takes new Value in to Account
var newMedian = default(T);
var newMedianSet = false;
var newValueInserted = false;
//keep track of each insertion
//insert newValue and existing values in sorted order
//to left and right nodes
//set new median during sorting
for (var i = 0; i < node.KeyCount; i++)
//if insertion count reached new median
//set the new median by picking the next smallest value
if (!newMedianSet && insertionCount == currentMedianIndex)
//median can be the new value or node.keys[i] (next node key)
if (!newValueInserted && newValue.CompareTo(node.Keys[i]) < 0)
if (newValueLeft != null)
SetChild(currentNode, currentNode.KeyCount, newValueLeft);
if (newValueRight != null)
SetChild(currentNode, 0, newValueRight);
newMedian = node.Keys[i];
//pick the smaller among newValue and node.Keys[i]
//and insert in to currentNode (left and right nodes)
//if new Value was already inserted then just copy from node.Keys in sequence
//since node.Keys is already in sorted order it should be fine
if (newValueInserted || node.Keys[i].CompareTo(newValue) < 0)
currentNode.Keys[currentNodeIndex] = node.Keys[i];
//if child is set don't set again
//the child was already set by last newValueRight or last node
if (currentNode.Children[currentNodeIndex] == null)
SetChild(currentNode, currentNodeIndex, node.Children[i]);
SetChild(currentNode, currentNodeIndex + 1, node.Children[i + 1]);
currentNode.Keys[currentNodeIndex] = newValue;
SetChild(currentNode, currentNodeIndex, newValueLeft);
SetChild(currentNode, currentNodeIndex + 1, newValueRight);
//could be that thew newKey is the greatest
currentNode.Keys[currentNodeIndex] = newValue;
SetChild(currentNode, currentNodeIndex, newValueLeft);
SetChild(currentNode, currentNodeIndex + 1, newValueRight);
//insert overflow element (newMedian) to parent
var parent = node.Parent;
InsertAndSplit(ref parent, newMedian, left, right);
/// Insert to a node that is not full
private static void InsertToNotFullNode(ref BTreeNode<T> node, T newValue,
BTreeNode<T> newValueLeft, BTreeNode<T> newValueRight)
for (var i = 0; i < node.KeyCount; i++)
if (newValue.CompareTo(node.Keys[i]) >= 0)
InsertAt(node.Keys, i, newValue);
SetChild(node, i, newValueLeft);
InsertChild(node, i + 1, newValueRight);
//newValue is the greatest
//element should be inserted at the end then
node.Keys[node.KeyCount] = newValue;
SetChild(node, node.KeyCount - 1, newValueLeft);
SetChild(node, node.KeyCount, newValueRight);
/// return the node containing max value which will be a leaf at the right most
private static BTreeNode<T> FindMinNode(BTreeNode<T> node)
return node.IsLeaf ? node : FindMinNode(node.Children[0]);
/// return the node containing max value which will be a leaf at the right most
private static BTreeNode<T> FindMaxNode(BTreeNode<T> node)
return node.IsLeaf ? node : FindMaxNode(node.Children[node.KeyCount]);
/// Balance a node which is short of Keys by rotations or merge
private void Balance(BTreeNode<T> node)
if (node == Root || node.KeyCount >= _minKeysPerNode)
var rightSibling = GetRightSibling(node);
&& rightSibling.KeyCount > _minKeysPerNode)
LeftRotate(node, rightSibling);
var leftSibling = GetLeftSibling(node);
&& leftSibling.KeyCount > _minKeysPerNode)
RightRotate(leftSibling, node);
if (rightSibling != null)
Sandwich(node, rightSibling);
Sandwich(leftSibling, node);
/// merge two adjacent siblings to one node
private void Sandwich(BTreeNode<T> leftSibling, BTreeNode<T> rightSibling)
var separatorIndex = GetNextSeparatorIndex(leftSibling);
var parent = leftSibling.Parent;
var newNode = new BTreeNode<T>(_maxKeysPerNode, leftSibling.Parent);
for (var i = 0; i < leftSibling.KeyCount; i++)
newNode.Keys[newIndex] = leftSibling.Keys[i];
if (leftSibling.Children[i] != null)
SetChild(newNode, newIndex, leftSibling.Children[i]);
if (leftSibling.Children[i + 1] != null)
SetChild(newNode, newIndex + 1, leftSibling.Children[i + 1]);
//special case when left sibling is empty
if (leftSibling.KeyCount == 0 && leftSibling.Children[0] != null)
SetChild(newNode, newIndex, leftSibling.Children[0]);
newNode.Keys[newIndex] = parent.Keys[separatorIndex];
for (var i = 0; i < rightSibling.KeyCount; i++)
newNode.Keys[newIndex] = rightSibling.Keys[i];
if (rightSibling.Children[i] != null)
SetChild(newNode, newIndex, rightSibling.Children[i]);
if (rightSibling.Children[i + 1] != null)
SetChild(newNode, newIndex + 1, rightSibling.Children[i + 1]);
//special case when left sibling is empty
if (rightSibling.KeyCount == 0 && rightSibling.Children[0] != null)
SetChild(newNode, newIndex, rightSibling.Children[0]);
newNode.KeyCount = newIndex;
SetChild(parent, separatorIndex, newNode);
RemoveAt(parent.Keys, separatorIndex);
RemoveChild(parent, separatorIndex + 1);
if (parent.KeyCount < _minKeysPerNode)
private static void RightRotate(BTreeNode<T> leftSibling, BTreeNode<T> rightSibling)
var parentIndex = GetNextSeparatorIndex(leftSibling);
InsertAt(rightSibling.Keys, 0, rightSibling.Parent.Keys[parentIndex]);
InsertChild(rightSibling, 0, leftSibling.Children[leftSibling.KeyCount]);
rightSibling.Parent.Keys[parentIndex] = leftSibling.Keys[leftSibling.KeyCount - 1];
RemoveAt(leftSibling.Keys, leftSibling.KeyCount - 1);
RemoveChild(leftSibling, leftSibling.KeyCount + 1);
private static void LeftRotate(BTreeNode<T> leftSibling, BTreeNode<T> rightSibling)
var parentIndex = GetNextSeparatorIndex(leftSibling);
leftSibling.Keys[leftSibling.KeyCount] = leftSibling.Parent.Keys[parentIndex];
SetChild(leftSibling, leftSibling.KeyCount, rightSibling.Children[0]);
leftSibling.Parent.Keys[parentIndex] = rightSibling.Keys[0];
RemoveAt(rightSibling.Keys, 0);
RemoveChild(rightSibling, 0);
/// Locate the node in which the item to delete exist
private static BTreeNode<T> FindDeletionNode(BTreeNode<T> node, T value)
//if leaf then its time to insert
for (var i = 0; i < node.KeyCount; i++)
if (value.CompareTo(node.Keys[i]) == 0)
//if not leaf then drill down to leaf
for (var i = 0; i < node.KeyCount; i++)
if (value.CompareTo(node.Keys[i]) == 0)
//current value is less than new value
//drill down to left child of current value
if (value.CompareTo(node.Keys[i]) < 0)
return FindDeletionNode(node.Children[i], value);
//current value is grearer than new value
//and current value is last element
if (node.KeyCount == i + 1)
return FindDeletionNode(node.Children[i + 1], value);
/// Get next key separator index after this child Node in parent
private static int GetNextSeparatorIndex(BTreeNode<T> node)
var parent = node.Parent;
if (node.Index == parent.KeyCount)
/// get the right sibling node
private static BTreeNode<T> GetRightSibling(BTreeNode<T> node)
var parent = node.Parent;
return node.Index == parent.KeyCount ? null : parent.Children[node.Index + 1];
/// get left sibling node
private static BTreeNode<T> GetLeftSibling(BTreeNode<T> node)
return node.Index == 0 ? null : node.Parent.Children[node.Index - 1];
private static void SetChild(BTreeNode<T> parent, int childIndex, BTreeNode<T> child)
parent.Children[childIndex] = child;
child.Index = childIndex;
private static void InsertChild(BTreeNode<T> parent, int childIndex, BTreeNode<T> child)
InsertAt(parent.Children, childIndex, child);
for (var i = childIndex; i <= parent.KeyCount; i++)
if (parent.Children[i] != null)
parent.Children[i].Index = i;
private static void RemoveChild(BTreeNode<T> parent, int childIndex)
RemoveAt(parent.Children, childIndex);
for (var i = childIndex; i <= parent.KeyCount; i++)
if (parent.Children[i] != null)
parent.Children[i].Index = i;
/// Shift array right at index to make room for new insertion
/// And then insert at index
/// Assumes array have atleast one empty index at end
private static void InsertAt<TS>(TS[] array, int index, TS newValue)
//shift elements right by one indice from index
Array.Copy(array, index, array, index + 1, array.Length - index - 1);
/// Shift array left at index
private static void RemoveAt<TS>(TS[] array, int index)
//shift elements right by one indice from index
Array.Copy(array, index + 1, array, index, array.Length - index - 1);
private class DelegateComparer : IValueComparer<T>
public DelegateComparer(Func<T, int> comparer)
public int CompareTo(T t)
private readonly Func<T, int> _comparer;
private class DefaultComparer : IValueComparer<T>
public DefaultComparer(T compareObject)
CompareObject = compareObject;
public int CompareTo(T t)
return CompareObject.CompareTo(t);
private T CompareObject { get; }
/// abstract node shared by both B and B+ tree nodes
/// so that we can use this for common tests across B and B+ tree
internal abstract class BNode<T> where T : IComparable
internal BNode(int maxKeysPerNode)
Keys = new T[maxKeysPerNode];
/// Array Index of this node in parent's Children array
internal int Index { set; get; }
internal T[] Keys { get; }
internal int KeyCount { get; set; }
//for common unit testing across B and B+ tree
internal abstract BNode<T> GetParent();
internal abstract BNode<T>[] GetChildren();
internal int GetMedianIndex()
return (KeyCount / 2) + 1;
internal class BTreeNode<T> : BNode<T> where T : IComparable
internal BTreeNode(int maxKeysPerNode, BTreeNode<T> parent)
Children = new BTreeNode<T>[maxKeysPerNode + 1];
internal BTreeNode<T> Parent { get; set; }
internal BTreeNode<T>[] Children { get; }
internal bool IsLeaf => Children[0] == null;
/// For shared test method accross B and B+ tree
internal override BNode<T> GetParent()
/// For shared test method accross B and B+ tree
internal override BNode<T>[] GetChildren()
internal class BTreeEnumerator<T> : IEnumerator<T> where T : IComparable
internal BTreeEnumerator(BTreeNode<T> root)
_progress = new Stack<BTreeNode<T>>(_root.Children.Take(_root.KeyCount + 1).Where(x => x != null));
return _current.KeyCount > 0;
if (_current != null && _index + 1 < _current.KeyCount)
_current = _progress.Pop();
foreach (var child in _current.Children.Take(_current.KeyCount + 1).Where(x => x != null))
object IEnumerator.Current => Current;
public T Current => _current.Keys[_index];
private readonly BTreeNode<T> _root;
private BTreeNode<T> _current;
private Stack<BTreeNode<T>> _progress;