这是情况,我正在开发一个二叉搜索树,并且在树的每个节点中,我打算存储它自己的高度,以便在avl树形成期间进一步平衡树。以前我有一个迭代方法来计算节点的平衡高度,如下所示。
(以下代码属于名为AVLTree<T>
的类,它是BinarySearchTree<T>
的子类
protected virtual int GetBalance(BinaryTreeNode<T> node)
{
if(node != null)
{
IEnumerable<BinaryTreeNode<T>> leftSubtree = null, righSubtree = null;
if (node.Left != null)
leftSubtree = node.Left.ToEnumerable(BinaryTreeTraversalType.InOrder);
if (node.Right != null)
righSubtree = node.Right.ToEnumerable(BinaryTreeTraversalType.InOrder);
var leftHeight = leftSubtree.IsNullOrEmpty() ? 0 : leftSubtree.Max(x => x.Depth) - node.Depth;
var righHeight = righSubtree.IsNullOrEmpty() ? 0 : righSubtree.Max(x => x.Depth) - node.Depth;
return righHeight - leftHeight;
}
return 0;
}
但它会产生很多性能开销。
Performance of an AVL Tree in C#
所以我在BinarySearchTree<T>
插入时将每个节点中的高度值存储起来。现在在平衡期间,我能够避免这种迭代,并且我在AVLTree<T>
中获得了所需的性能。
但现在问题是如果我尝试在BinarySearchTree<T>
中顺序插入大量数据1-50000(不平衡它),我得到StackoverflowException。我正在提供导致它的代码。你能帮我找一个能避免这个例外的解决方案,也不会影响其子类AVLTree<T>
的表现吗?
public class BinaryTreeNode<T>
{
private BinaryTreeNode<T> _left, _right;
private int _height;
public T Value {get; set; }
public BinaryTreeNode<T> Parent;
public int Depth {get; set; }
public BinaryTreeNode()
{}
public BinaryTreeNode(T data)
{
Value = data;
}
public BinaryTreeNode<T> Left
{
get { return _left; }
set
{
_left = value;
if (_left != null)
{
_left.Depth = Depth + 1;
_left.Parent = this;
}
UpdateHeight();
}
}
public BinaryTreeNode<T> Right
{
get { return _right; }
set
{
_right = value;
if (_right != null)
{
_right.Depth = Depth + 1;
_right.Parent = this;
}
UpdateHeight();
}
}
public int Height
{
get { return _height; }
protected internal set
{
_height = value;
if (Parent != null) {
Parent.UpdateHeight();
}
}
}
private void UpdateHeight()
{
if (Left == null && Right == null) {
return;
}
if(Left != null && Right != null)
{
if (Left.Height > Right.Height)
Height = Left.Height + 1;
else
Height = Right.Height + 1;
}
else if(Left == null)
Height = Right.Height + 1;
else
Height = Left.Height + 1;
}
}
public class BinarySearchTree<T>
{
private readonly Comparer<T> _comparer = Comparer<T>.Default;
public BinarySearchTree()
{
}
public BinaryTreeNode<T> Root {get; set;}
public virtual void Add(T value)
{
var n = new BinaryTreeNode<T>(value);
int result;
BinaryTreeNode<T> current = Root, parent = null;
while (current != null)
{
result = _comparer.Compare(current.Value, value);
if (result == 0)
{
parent = current;
current = current.Left;
}
if (result > 0)
{
parent = current;
current = current.Left;
}
else if (result < 0)
{
parent = current;
current = current.Right;
}
}
if (parent == null)
Root = n;
else
{
result = _comparer.Compare(parent.Value, value);
if (result > 0)
parent.Left = n;
else
parent.Right = n;
}
}
}
我在计算下一行的高度时遇到StackoverflowException
if (Parent != null) {
Parent.UpdateHeight();
}
在Height
类的BinaryTreeNode<T>
属性中。如果可能的话,请建议我解决一些问题。
答案 0 :(得分:2)
添加节点时,通过递归迭代所有父节点来计算高度。 .NET进程具有有限的堆栈空间,并且给定一个大树,您将占用所有堆栈空间并获得StackOverflowException
。您可以将递归更改为迭代以避免占用堆栈空间。其他语言(如函数式语言)可以使用称为尾递归的技术在不消耗堆栈空间的情况下进行递归。但是,在C#中,您必须手动修改代码。
以下是Height
中不使用递归的UpdateHeight
和BinaryTreeNode<T>
的修改版本:
public int Height {
get { return _height; }
private set { _height = value; }
}
void UpdateHeight() {
var leftHeight = Left != null ? Left.Height + 1 : 0;
var rightHeight = Right != null ? Right.Height + 1 : 0;
var height = Math.Max(leftHeight, rightHeight);
var node = this;
while (node != null) {
node.Height = height;
height += 1;
node = node.Parent;
}
}
答案 1 :(得分:0)
你可以添加尾巴。在il中调用,反编译该文件,然后再次编译。
示例:
.... IL_0002:添加
<强>尾。强>
IL_0003:致电......
IL_0008:ret
再次编译的例子:
ilasm C:\ test.il /out=C:\TestTail.exe
(这可能不是你想要的,但它只是一个例子)
我相信你可以弄明白并让它发挥作用,但这并不难。
最大的缺点是重新编译会消除你的尾部调用,所以我建议在msbuild中设置一个构建任务来为你自动完成。
答案 2 :(得分:0)
我想我找到了解决方案,我修改了代码如下,它就像一个魅力
public int Height
{
get { return _height; }
protected internal set
{
_height = value;
}
}
private void UpdateHeight()
{
if (Left == null && Right == null) {
return;
}
if(Left != null && Right != null)
{
if (Left.Height > Right.Height)
Height = Left.Height + 1;
else
Height = Right.Height + 1;
}
else if(Left == null)
Height = Right.Height + 1;
else
Height = Left.Height + 1;
var parent = Parent;
while (parent != null) {
parent.Height++;
parent = parent.Parent;
}
}
非常感谢那些花时间让我尝试找出解决方案的人。
答案 3 :(得分:0)
如果您要一次性插入大量数据,我认为您最好不要调用Parent.UpdateHeight批量插入数据,然后在树上设置高度。
添加未来的节点我会走在树上,从根开始,随着时间的推移递增高度。