我在浏览Roslyn来源时遇到了以下算法。该文件是Microsoft.CodeAnalysis.Formatting命名空间https://github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/Portable/Formatting/ContextIntervalTree.cs中的ContextIntervalTree.cs。我想更好地理解它,因为我在自己的工作中有类似算法的应用程序。该算法运行的树是由存储在每个节点中的间隔的起始端点排序的平衡二叉树。在此函数中,参数谓词测试查询间隔是包含端点还是包含端点。很少有东西会让我失望。
在下降树的同时,对end < MaxEndValue(right)
进行了测试。鉴于我们可能会考虑间隔,这不应该是<=
测试,就像左侧下降一样吗?
在测试中检查更好的答案为什么需要Introspector.GetStart(result) <= Introspector.GetStart(currentNode.Value)
,我认为只需测试较小的长度就足够了。
在我看过的其他算法中(例如http://www.geeksforgeeks.org/interval-tree/)如果左边可以保留答案,则首先搜索它,如果我们知道答案不是左子树,则只搜索右边。所以只搜索一方或另一方,而不是两者。
在提升树时,如果内循环被打破,控制将返回到out循环的顶部,这可能会再次开始下降树。是什么阻止算法在升序和降序之间交替?
我觉得这个算法会更直观,如果它可以递归写,但我不知道该如何去做。
private T GetSmallestContainingIntervalWorker(int start, int length, Func<T, int, int, bool> predicate)
{
var result = default(T);
if (root == null || MaxEndValue(root) < start)
{
return result;
}
int end = start + length;
// * our interval tree is a binary tree that is ordered by a start position.
//
// this method works by
// 1. find a sub tree that has biggest "start" position that is smaller than given "start" by going down right side of a tree
// 2. once it encounters a right sub tree that it can't go down anymore, move down to left sub tree once and try #1 again
// 3. once it gets to the position where it can't find any smaller span (both left and
// right sub tree doesn't contain given span) start to check whether current node
// contains the given "span"
// 4. move up the spin until it finds one that contains the "span" which should be smallest span that contains the given "span"
// 5. if it is going up from right side, it make sure to check left side of tree first.
using (var pooledObject = SharedPools.Default<Stack<Node>>().GetPooledObject())
{
var spineNodes = pooledObject.Object;
spineNodes.Push(root);
while (spineNodes.Count > 0)
{
var currentNode = spineNodes.Peek();
// only goes to right if right tree contains given span
if (Introspector.GetStart(currentNode.Value) <= start)
{
var right = currentNode.Right;
if (right != null && end < MaxEndValue(right))
{
spineNodes.Push(right);
continue;
}
}
// right side, sub tree doesn't contain the given span, put current node on
// stack, and move down to left sub tree
var left = currentNode.Left;
if (left != null && end <= MaxEndValue(left))
{
spineNodes.Push(left);
continue;
}
// we reached the point, where we can't go down anymore.
// now, go back up to find best answer
while (spineNodes.Count > 0)
{
currentNode = spineNodes.Pop();
// check whether current node meets condition
if (predicate(currentNode.Value, start, length))
{
// hold onto best answer
if (EqualityComparer<T>.Default.Equals(result, default) ||
(Introspector.GetStart(result) <= Introspector.GetStart(currentNode.Value) &&
Introspector.GetLength(currentNode.Value) < Introspector.GetLength(result)))
{
result = currentNode.Value;
}
}
// there is no parent, result we currently have is the best answer
if (spineNodes.Count == 0)
{
return result;
}
var parentNode = spineNodes.Peek();
// if we are under left side of parent node
if (parentNode.Left == currentNode)
{
// go one level up again
continue;
}
// okay, we are under right side of parent node
if (parentNode.Right == currentNode)
{
// try left side of parent node if it can have better answer
if (parentNode.Left != null && end <= MaxEndValue(parentNode.Left))
{
// right side tree doesn't have any answer or if the right side has
// an answer but left side can have better answer then try left side
if (EqualityComparer<T>.Default.Equals(result, default) ||
Introspector.GetStart(parentNode.Value) == Introspector.GetStart(currentNode.Value))
{
// put left as new root, and break out inner loop
spineNodes.Push(parentNode.Left);
break;
}
}
// no left side, go one more level up
continue;
}
}
}
return result;
}
}