如何避免此stackoverflow异常?

时间:2010-09-10 08:31:06

标签: c# .net recursion performance stack-overflow

这是情况,我正在开发一个二叉搜索树,并且在树的每个节点中,我打算存储它自己的高度,以便在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>属性中。如果可能的话,请建议我解决一些问题。

顺便说一句,非常感谢你注意阅读这么长的问题:)

4 个答案:

答案 0 :(得分:2)

添加节点时,通过递归迭代所有父节点来计算高度。 .NET进程具有有限的堆栈空间,并且给定一个大树,您将占用所有堆栈空间并获得StackOverflowException。您可以将递归更改为迭代以避免占用堆栈空间。其他语言(如函数式语言)可以使用称为尾递归的技术在不消耗堆栈空间的情况下进行递归。但是,在C#中,您必须手动修改代码。

以下是Height中不使用递归的UpdateHeightBinaryTreeNode<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:re​​t

再次编译的例子:

  

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批量插入数据,然后在树上设置高度。

添加未来的节点我会走在树上,从根开始,随着时间的推移递增高度。