Add a weight-balanced tree
This commit is contained in:
parent
351697d3c9
commit
dbd40bd034
164
Quadratic.Carto.Craft2/Containers/WBTree.cs
Normal file
164
Quadratic.Carto.Craft2/Containers/WBTree.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Quadratic.Carto.Craft2.Containers;
|
||||
|
||||
/// <summary>
|
||||
/// Weight-balanced tree. Immutable.
|
||||
///
|
||||
/// Also see: <a href="https://en.wikipedia.org/wiki/Weight-balanced_tree">Wikipedia</a>,
|
||||
/// the <a href="https://dl.acm.org/doi/pdf/10.1145/1499949.1500040">original paper</a>,
|
||||
/// and the <a href="https://yoichihirai.com/bst.pdf">paper</a> by Hirai about selecting the right balance factor.
|
||||
/// </summary>
|
||||
public struct WBTree<T> where T : IComparable<T>
|
||||
{
|
||||
Node? root;
|
||||
|
||||
class Node(T value, Node? left = null, Node? right = null)
|
||||
{
|
||||
public int Size { get; } = NSize(left) + NSize(right) + 1;
|
||||
public T Value { get; set; } = value;
|
||||
public Node? Left { get; } = left;
|
||||
public Node? Right { get; } = right;
|
||||
|
||||
public void Deconstruct(out T value, out Node? left, out Node? right)
|
||||
{
|
||||
value = this.Value;
|
||||
left = this.Left;
|
||||
right = this.Right;
|
||||
}
|
||||
|
||||
public bool Balanced()
|
||||
{
|
||||
return IsBalanced(Left, Right) && IsBalanced(Right, Left) &&
|
||||
(Left?.Balanced() ?? true) && (Right?.Balanced() ?? true);
|
||||
}
|
||||
|
||||
private static int NSize(Node? n) => n?.Size ?? 0;
|
||||
|
||||
private static bool IsBalanced(Node? l, Node? r) =>
|
||||
DELTA * (NSize(l) + 1) >= (NSize(r) + 1);
|
||||
|
||||
private static bool IsSingle(Node? l, Node? r) =>
|
||||
(NSize(l) + 1) < GAMMA * (NSize(r) + 1);
|
||||
|
||||
private const int DELTA = 2;
|
||||
private const int GAMMA = 3;
|
||||
|
||||
public static Node Insert(Node? node, T value)
|
||||
{
|
||||
if (node == null) return new Node(value);
|
||||
var cmp = value.CompareTo(node.Value);
|
||||
if (cmp < 0)
|
||||
return BalanceR(node.Value, Insert(node.Left, value), node.Right);
|
||||
else if (cmp > 0)
|
||||
return BalanceL(node.Value, node.Left, Insert(node.Right, value));
|
||||
else
|
||||
return new Node(value, node.Left, node.Right);
|
||||
}
|
||||
|
||||
private static Node BalanceL(T v, Node? l, Node r)
|
||||
{
|
||||
if (IsBalanced(l, r)) return new Node(v, l, r);
|
||||
// rotateL
|
||||
else if (IsSingle(l, r))
|
||||
{
|
||||
var (k2, t2, t3) = r;
|
||||
return new Node(k2, new Node(v, l, t2), t3);
|
||||
}
|
||||
else
|
||||
{
|
||||
var (k2, rl, t4) = r;
|
||||
if (rl is null)
|
||||
throw new ArgumentException("Unable to perform double rotation; r.Left is null");
|
||||
var (k3, t2, t3) = rl;
|
||||
return new Node(k3, new Node(v, l, t2), new Node(k2, t3, t4));
|
||||
}
|
||||
}
|
||||
|
||||
private static Node BalanceR(T cVal, Node l, Node? r)
|
||||
{
|
||||
if (IsBalanced(r, l)) return new Node(cVal, l, r);
|
||||
// rotateR
|
||||
else if (IsSingle(r, l))
|
||||
{
|
||||
var (k2, t2, t3) = l;
|
||||
return new Node(k2, t2, new Node(cVal, t3, r));
|
||||
}
|
||||
else
|
||||
{
|
||||
var (k2, t2, lr) = l;
|
||||
if (lr is null)
|
||||
throw new ArgumentException("Unable to perform double rotation; l.Right is null");
|
||||
var (k3, t3, t4) = lr;
|
||||
return new Node(k3, new Node(k2, t2, t3), new Node(cVal, t4, r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WBTree(Node? node)
|
||||
{
|
||||
this.root = node;
|
||||
}
|
||||
|
||||
public static WBTree<T> Empty => new WBTree<T>(null);
|
||||
|
||||
public static WBTree<T> Singleton(T value) => new WBTree<T>(new Node(value));
|
||||
|
||||
public readonly int Count => root is null ? 0 : root.Size;
|
||||
|
||||
public WBTree<T> Add(T value) => new WBTree<T>(Node.Insert(root, value));
|
||||
|
||||
public bool TryGet(T value, [NotNullWhen(true)] out T? result)
|
||||
{
|
||||
var node = root;
|
||||
while (node != null)
|
||||
{
|
||||
var cmp = value.CompareTo(node.Value);
|
||||
if (cmp < 0)
|
||||
node = node.Left;
|
||||
else if (cmp > 0)
|
||||
node = node.Right;
|
||||
else
|
||||
{
|
||||
result = node.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Contains(T value) => TryGet(value, out _);
|
||||
|
||||
public T Get(T value) => TryGet(value, out var result) ? result : throw new KeyNotFoundException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A mutable reference to a weight-balanced tree. Might be preferable to use in some cases.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class WBTreeRef<T> where T : IComparable<T>
|
||||
{
|
||||
WBTree<T> impl;
|
||||
|
||||
public WBTreeRef() => impl = WBTree<T>.Empty;
|
||||
|
||||
public WBTreeRef(T value) => impl = WBTree<T>.Singleton(value);
|
||||
|
||||
public int Count => impl.Count;
|
||||
|
||||
public void Add(T value) => impl = impl.Add(value);
|
||||
|
||||
public bool TryGet(T value, [NotNullWhen(true)] out T? result) => impl.TryGet(value, out result);
|
||||
|
||||
public bool Contains(T value) => impl.Contains(value);
|
||||
|
||||
public T Get(T value) => impl.Get(value);
|
||||
|
||||
public WBTree<T> ToImmutable() => impl;
|
||||
|
||||
public static implicit operator WBTreeRef<T>(WBTree<T> tree) => new() { impl = tree };
|
||||
|
||||
public static implicit operator WBTree<T>(WBTreeRef<T> tree) => tree.impl;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user