mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-05-27 18:42:45 -05:00
400 lines
16 KiB
C#
400 lines
16 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
namespace HavenSoft.HexManiac.Core.Models {
|
|
public interface ISearchTreePayload {
|
|
int Start { get; }
|
|
}
|
|
|
|
public enum TreeColor { Red, Black }
|
|
public enum AddType { Left, Right, Balanced, Insert, ReplaceExisting }
|
|
public enum RemoveType { NoRemoval, Balanced, DecreaseBlackCount }
|
|
|
|
// Red-Black tree rules:
|
|
// (1) The root is black
|
|
// (2) All red nodes have black parents
|
|
// (3) The path to every limb has the same number of blacks.
|
|
|
|
/// <summary>
|
|
/// Implements a sorted collection with methods for storing and retrieving elements.
|
|
/// Unlike a SortedDictionary<>, you can get a value using a key that's not in the dictionary.
|
|
/// In such a case, you get the value with the _next_ start position.
|
|
/// </summary>
|
|
public class SearchTree<T> : IEnumerable<T> where T : class, ISearchTreePayload {
|
|
public const int LEFT = 0, RIGHT = 1;
|
|
private TreeNode<T> root;
|
|
|
|
public int Count { get; private set; }
|
|
|
|
public void Add(T element) {
|
|
if (TreeNode.Add(ref root, element)) Count++;
|
|
}
|
|
|
|
public void Remove(int start) {
|
|
if (TreeNode.Remove(ref root, start)) Count--;
|
|
}
|
|
|
|
public void Clear() {
|
|
root = null;
|
|
Count = 0;
|
|
}
|
|
|
|
public SearchPath this[int index] {
|
|
get {
|
|
var directions = new List<int>();
|
|
for (var node = root; node != null;) {
|
|
if (index < node.Payload.Start) {
|
|
directions.Add(LEFT);
|
|
node = node.Left;
|
|
} else if (node.Payload.Start < index) {
|
|
directions.Add(RIGHT);
|
|
node = node.Right;
|
|
} else {
|
|
return new SearchPath(this, directions, node.Payload);
|
|
}
|
|
}
|
|
|
|
// not found
|
|
return new SearchPath(this, directions);
|
|
}
|
|
}
|
|
|
|
public T this[SearchPath path] => path.Element;
|
|
|
|
private IList<TreeNode<T>> GetNodesOnPath(IEnumerable<int> path) {
|
|
var results = new List<TreeNode<T>>();
|
|
var node = root;
|
|
results.Add(node);
|
|
foreach (int direction in path) {
|
|
if (direction == LEFT) node = node.Left;
|
|
else node = node.Right;
|
|
if (node == null) break;
|
|
results.Add(node);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public IEnumerable<T> StartingFrom(int index) {
|
|
return root.EnumerateFrom(index);
|
|
}
|
|
|
|
public IEnumerator<T> GetEnumerator() {
|
|
foreach (var node in root) yield return node.Payload;
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
public class SearchPath {
|
|
private readonly SearchTree<T> tree;
|
|
private readonly IList<int> directions;
|
|
|
|
public T PreviousElement => GetPrevious().Payload;
|
|
public T Element { get; }
|
|
public T NextElement => GetNext().Payload;
|
|
public bool HasElement => Element != null;
|
|
|
|
public SearchPath(SearchTree<T> tree, IList<int> directions, T element = null) {
|
|
this.tree = tree;
|
|
Element = element;
|
|
this.directions = directions;
|
|
}
|
|
|
|
#region operators
|
|
|
|
public static bool operator >=(SearchPath path, int index) {
|
|
if (index == 0) return path.HasElement;
|
|
throw new NotImplementedException();
|
|
}
|
|
public static bool operator <=(SearchPath path, int inedx) {
|
|
throw new NotImplementedException();
|
|
}
|
|
public static bool operator <(SearchPath path, int index) {
|
|
if (index == 0) return !path.HasElement;
|
|
throw new NotImplementedException();
|
|
}
|
|
public static bool operator >(SearchPath path, int index) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
#endregion
|
|
|
|
private TreeNode<T> GetPrevious() {
|
|
var path = tree.GetNodesOnPath(directions);
|
|
if (!HasElement && directions.Last() == RIGHT) return path.Last();
|
|
|
|
int checkNodeIndex = path.Count - 1;
|
|
|
|
var previous = path[checkNodeIndex].Left;
|
|
if (previous == null) {
|
|
while (checkNodeIndex > 0 && path[checkNodeIndex - 1].Left == path[checkNodeIndex]) checkNodeIndex--;
|
|
if (checkNodeIndex == 0) return null;
|
|
previous = path[checkNodeIndex - 1].Left;
|
|
}
|
|
|
|
while (previous.Right != null) previous = previous.Right;
|
|
return previous;
|
|
}
|
|
|
|
private TreeNode<T> GetNext() {
|
|
var path = tree.GetNodesOnPath(directions);
|
|
if (!HasElement && directions.Last() == LEFT) return path.Last();
|
|
|
|
int checkNodeIndex = path.Count - 1;
|
|
|
|
var next = path[checkNodeIndex].Right;
|
|
if (next == null) {
|
|
while (checkNodeIndex > 0 && path[checkNodeIndex - 1].Right == path[checkNodeIndex]) checkNodeIndex--;
|
|
if (checkNodeIndex == 0) return null;
|
|
next = path[checkNodeIndex - 1].Right;
|
|
}
|
|
|
|
while (next.Left != null) next = next.Left;
|
|
return next;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class TreeNode {
|
|
public static TreeNode<T> From<T>(T element) where T : ISearchTreePayload => new TreeNode<T>(element);
|
|
/// <returns>True if the size of the collection grew.</returns>
|
|
public static bool Add<T>(ref TreeNode<T> node, T element) where T : ISearchTreePayload => TreeNode<T>.Add(ref node, element) != AddType.ReplaceExisting;
|
|
/// <returns>True if a node was actually removed.</returns>
|
|
public static bool Remove<T>(ref TreeNode<T> node, int key) where T : ISearchTreePayload => TreeNode<T>.Remove(ref node, key) != RemoveType.NoRemoval;
|
|
public static void RotateRight<T>(ref TreeNode<T> node) where T : ISearchTreePayload {
|
|
var newRoot = node.Left;
|
|
node.Left = newRoot.Right;
|
|
newRoot.Right = node;
|
|
node = newRoot;
|
|
}
|
|
public static void RotateLeft<T>(ref TreeNode<T> node) where T : ISearchTreePayload {
|
|
var newRoot = node.Right;
|
|
node.Right= newRoot.Left;
|
|
newRoot.Left = node;
|
|
node = newRoot;
|
|
}
|
|
public static void Rotate<T>(ref TreeNode<T> node, int direction) where T : ISearchTreePayload {
|
|
if (direction == TreeNode<T>.LEFT) RotateLeft(ref node);
|
|
else RotateRight(ref node);
|
|
}
|
|
public static bool IsBlack<T>(this TreeNode<T> node) where T : ISearchTreePayload => (node?.Color ?? TreeColor.Black) == TreeColor.Black;
|
|
public static bool BlackWithBlackChildren<T>(this TreeNode<T> node)where T : ISearchTreePayload {
|
|
if (node == null || node.Color == TreeColor.Red) return false;
|
|
return node.Left.IsBlack() && node.Right.IsBlack();
|
|
}
|
|
}
|
|
|
|
public class TreeNode<T> : IEnumerable<TreeNode<T>> where T : ISearchTreePayload {
|
|
public const int LEFT = 0, RIGHT = 1;
|
|
|
|
public T Payload { get; private set; }
|
|
public TreeColor Color { get; private set; }
|
|
public int BlackCount => ((Left == null) ? 0 : Left.BlackCount) + (Color == TreeColor.Black ? 1 : 0);
|
|
private TreeNode<T>[] children = new TreeNode<T>[2];
|
|
public TreeNode<T> Left { get => children[0]; set => children[0] = value; }
|
|
public TreeNode<T> Right { get => children[1]; set => children[1] = value; }
|
|
public TreeNode(T value) => Payload = value;
|
|
|
|
public override string ToString() {
|
|
var result = Payload.ToString();
|
|
if (Color == TreeColor.Red) return $"({result})";
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks to make sure that this red-black subtree obeys the no-double-red rule and the black-height-match rule.
|
|
/// </summary>
|
|
[Conditional("DEBUG")]
|
|
public void Verify() => VerifyHelper();
|
|
|
|
private int VerifyHelper() {
|
|
// verify that from this node, the black length is the same in both directions
|
|
// verify that if this node is red, it has no red children.
|
|
var leftCount = children[LEFT] == null ? 0 : Left.VerifyHelper();
|
|
var rightCount = Right == null ? 0 : Right.VerifyHelper();
|
|
Debug.Assert(leftCount == rightCount, $"RedBlack Node must have matching black count on left and right branches: {leftCount} and {rightCount}");
|
|
if (Color == TreeColor.Black) return leftCount + 1;
|
|
Debug.Assert(Left.IsBlack() && Right.IsBlack(), "Red nodes must not have red children!");
|
|
return leftCount;
|
|
}
|
|
|
|
public bool BlackWithRedChild(int direction) => this.IsBlack() && !children[direction].IsBlack();
|
|
|
|
public IEnumerable<T> EnumerateFrom(int start) {
|
|
if (start > Payload.Start && Left != null) {
|
|
foreach (var node in Left.EnumerateFrom(start)) yield return node;
|
|
}
|
|
if (start == Payload.Start) yield return Payload;
|
|
if (Right != null) {
|
|
foreach (var node in Right.EnumerateFrom(start)) yield return node;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only do tree-balancing if the current node is red.
|
|
/// Returns true if the higher level should perform balancing.
|
|
/// </summary>
|
|
public static AddType Add(ref TreeNode<T> node, T element) {
|
|
var insert = TreeNode.From(element);
|
|
return Add(ref node, insert);
|
|
}
|
|
|
|
public static AddType Add(ref TreeNode<T> node, TreeNode<T> insert, bool balance = true) {
|
|
var start = insert.Payload.Start;
|
|
if (node == null || node.Payload.Start == start) {
|
|
if (balance) insert.Color = node?.Color ?? TreeColor.Red;
|
|
var addType = balance ? AddType.Insert : AddType.Balanced;
|
|
if (node != null) addType = AddType.ReplaceExisting;
|
|
insert.Left = node?.Left;
|
|
insert.Right = node?.Right;
|
|
node = insert;
|
|
return addType;
|
|
} else if (start < node.Payload.Start) {
|
|
return AddChild(ref node, insert, LEFT, balance);
|
|
} else if (node.Payload.Start < start) {
|
|
return AddChild(ref node, insert, RIGHT, balance);
|
|
} else {
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private static AddType AddChild(ref TreeNode<T> parent, TreeNode<T> insert, int direction, bool balance) {
|
|
var other = 1 - direction;
|
|
var addMethod = Add(ref parent.children[direction], insert, balance);
|
|
if (addMethod == AddType.ReplaceExisting) return addMethod;
|
|
if (addMethod == AddType.Insert) return (AddType)direction;
|
|
var (node, sibling) = (parent.children[direction], parent.children[other]);
|
|
if (addMethod != AddType.Balanced) {
|
|
if (node.RecolorIfSelfAndSiblingAreRed(sibling, parent)) return AddType.Insert;
|
|
if (addMethod == (AddType)other && !node.IsBlack() && sibling.IsBlack()) {
|
|
TreeNode.Rotate(ref parent.children[direction], direction);
|
|
node = parent.children[direction];
|
|
}
|
|
node.RotateIfSelfRedWithBlackSibling(sibling, ref parent, other);
|
|
}
|
|
return AddType.Balanced;
|
|
}
|
|
|
|
public static RemoveType Remove(ref TreeNode<T> node, int key) {
|
|
if (node == null) return RemoveType.NoRemoval;
|
|
|
|
if (key == node.Payload.Start) {
|
|
if (node.Right != null) {
|
|
var nextNode = node.Right.First();
|
|
(node.Payload, nextNode.Payload) = (nextNode.Payload, node.Payload);
|
|
return RemoveChild(ref node, key, RIGHT);
|
|
} else if (node.Left != null) {
|
|
var prevNode = node.Left; // if I have only one child, by the red-black rules it must be a Red leaf
|
|
(node.Payload, prevNode.Payload) = (prevNode.Payload, node.Payload);
|
|
return RemoveChild(ref node, key, LEFT);
|
|
} else {
|
|
var color = node.Color;
|
|
node = null;
|
|
return color == TreeColor.Red ? RemoveType.Balanced : RemoveType.DecreaseBlackCount; ;
|
|
}
|
|
}
|
|
|
|
if (node.Payload.Start < key) {
|
|
return RemoveChild(ref node, key, RIGHT);
|
|
} else {
|
|
return RemoveChild(ref node, key, LEFT);
|
|
}
|
|
}
|
|
|
|
private static RemoveType RemoveChild(ref TreeNode<T> node, int key, int direction) {
|
|
int other = 1 - direction;
|
|
var result = Remove(ref node.children[direction], key);
|
|
if (result == RemoveType.NoRemoval) return result;
|
|
if (node.Color == TreeColor.Black && !node.children[other].IsBlack() && result == RemoveType.DecreaseBlackCount) {
|
|
TreeNode.Rotate(ref node, direction);
|
|
node.Recolor();
|
|
node.children[direction].Recolor();
|
|
IncreaseBlackCount(ref node.children[direction], direction);
|
|
return RemoveType.Balanced;
|
|
}
|
|
if (node.Color == TreeColor.Black && result == RemoveType.DecreaseBlackCount && node.children[other].BlackWithBlackChildren()) {
|
|
node.children[other].Recolor();
|
|
return RemoveType.DecreaseBlackCount;
|
|
} else if (node.Color == TreeColor.Red && result == RemoveType.DecreaseBlackCount && node.children[other].IsBlack() && node.children[direction].IsBlack()) {
|
|
IncreaseBlackCount(ref node, direction);
|
|
return RemoveType.Balanced;
|
|
}
|
|
|
|
if (node.Color == TreeColor.Black && result == RemoveType.DecreaseBlackCount && node.children[other].BlackWithRedChild(other)) {
|
|
TreeNode.Rotate(ref node, direction);
|
|
node.children[other].Recolor();
|
|
return RemoveType.Balanced;
|
|
}
|
|
if (node.Color == TreeColor.Black && result == RemoveType.DecreaseBlackCount && node.children[other].BlackWithRedChild(direction)) {
|
|
// this case just rotates to become the previous case
|
|
TreeNode.Rotate(ref node.children[other], other);
|
|
node.children[other].children[other].Recolor();
|
|
node.children[other].Recolor();
|
|
|
|
TreeNode.Rotate(ref node, direction);
|
|
node.children[other].Recolor();
|
|
return RemoveType.Balanced;
|
|
}
|
|
|
|
return RemoveType.Balanced;
|
|
}
|
|
|
|
private static void IncreaseBlackCount(ref TreeNode<T> node, int direction) {
|
|
if (node.Color == TreeColor.Red && node.children[1 - direction].BlackWithRedChild(direction)) {
|
|
TreeNode.Rotate(ref node.children[1 - direction], 1 - direction);
|
|
node.children[1 - direction].Recolor();
|
|
node.children[1 - direction].children[1 - direction].Recolor();
|
|
}
|
|
if (node.Color == TreeColor.Red && node.Left.IsBlack() && node.Right.IsBlack()) {
|
|
node.Recolor();
|
|
node.children[1 - direction].Recolor();
|
|
}
|
|
if (node.children[1 - direction].children.Any(child => !child.IsBlack())) {
|
|
TreeNode.Rotate(ref node, direction);
|
|
node.children[direction].Recolor();
|
|
node.Recolor();
|
|
}
|
|
}
|
|
|
|
public void Recolor() => Color = Color == TreeColor.Red ? TreeColor.Black : TreeColor.Red;
|
|
|
|
public IEnumerator<TreeNode<T>> GetEnumerator() {
|
|
if (Left != null) {
|
|
foreach (var node in Left) {
|
|
yield return node;
|
|
}
|
|
}
|
|
yield return this;
|
|
if (Right != null) {
|
|
foreach (var node in Right) {
|
|
yield return node;
|
|
}
|
|
}
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
private bool RecolorIfSelfAndSiblingAreRed(TreeNode<T> sibling, TreeNode<T> parent) {
|
|
if (!this.IsBlack() && !sibling.IsBlack()) {
|
|
// parent is guaranteed to be black by rule (2)
|
|
parent.Recolor();
|
|
Recolor();
|
|
sibling.Recolor();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void RotateIfSelfRedWithBlackSibling(TreeNode<T> sibling, ref TreeNode<T> parent, int rotateDirection) {
|
|
if (parent == null) return; // root is always black
|
|
if (!sibling.IsBlack()) return;
|
|
if (this.IsBlack()) return;
|
|
TreeNode.Rotate(ref parent, rotateDirection);
|
|
parent.children[rotateDirection].Recolor();
|
|
parent.Recolor();
|
|
}
|
|
}
|
|
}
|