使用派生类型进行递归树方法

huangapple go评论57阅读模式
英文:

Using derived type for recursive tree method

问题

我有一个名为BinaryTree<T>的类,看起来像这样:

class BinaryTree<T> 
        where T : IComparable
    {
        protected BinaryTree<T>? _left;
        protected BinaryTree<T>? _right;
        protected BinaryTree<T>? _root;
        protected T _value;

        public T Value
        {
            get { return _value; }
            set { _value = value; }
        }

        public BinaryTree<T>? Left
        {
            get { return _left; }
            set { _left = value; }
        }

        public BinaryTree<T>? Right
        {
            get { return _right; }
            set { _right = value; }
        }

        public BinaryTree<T>? Root
        {
            get { return _root; }
            set { _root = value; }
        }
      
        public BinaryTree() { }

        public BinaryTree(T value, BinaryTree<T>? left = null, BinaryTree<T>? right = null, BinaryTree<T>? root = null)
        { ... }
        public List<T> PreOrder()
        { ... }

        public List<T> InOrder()
        { ... }

        public List<T> PostOrder()
        { ... }
    }

还有一个派生类BinarySearchTree,它实现了Search方法:

class BinarySearchTree : BinaryTree<int>
    {
        public BinarySearchTree() : base() { } 

        public Boolean Search(int value)
        {
            if (Value.CompareTo(value) == 0) return true;
            if ((Left is not null) && (Value.CompareTo(value) > 0)) { return Left.Search(value); }
            else if ((Right is not null) && (Value.CompareTo(value) > 0)) { return Right.Search(value); }
            else { return false; }
        }
    }

Left.Search(和类似地Right)不起作用,因为Left的类型不是BinarySearchTree,而是BinaryTree<int>。我知道可以通过在BinaryTree中覆盖_left_rightLeftRight来解决这个问题,但这似乎是多余的,因为新代码在功能上没有任何不同。我也知道我们可以在这种情况下将Search移动到BinaryTree中,但这不是重点;对于我只想在派生类中拥有的更复杂方法来说,似乎应该在某个地方提供这种功能。

我最初的想法是尝试将基类中的_left_right的类型设置为派生类的类型。但似乎不可能;我可以使用GetType()来获取类型的字符串,但无法使用该类型定义属性。

英文:

I have a class BinaryTree&lt;T&gt; that looks like this:

 class BinaryTree&lt;T&gt; 
        where T : IComparable
    {
        protected BinaryTree&lt;T&gt;? _left;
        protected BinaryTree&lt;T&gt;? _right;
        protected BinaryTree&lt;T&gt;? _root;
        protected T _value;

        public T Value
        {
            get { return _value; }
            set { _value = value; }
        }

        public BinaryTree&lt;T&gt;? Left
        {
            get { return _left; }
            set { _left = value; }
        }

        public BinaryTree&lt;T&gt;? Right
        {
            get { return _right; }
            set { _right = value; }
        }

        public BinaryTree&lt;T&gt;? Root
        {
            get { return _root; }
            set { _root = value; }
        }
      
        public BinaryTree() { }

        public BinaryTree(T value, BinaryTree&lt;T&gt;? left = null, BinaryTree&lt;T&gt;? right = null, BinaryTree&lt;T&gt;? root = null)
        { ... }
        public List&lt;T&gt; PreOrder()
        { ... }

        public List&lt;T&gt; InOrder()
        { ... }

        public List&lt;T&gt; PostOrder()
        { ... }
    }

And a dervied class BinarySearchTree that implements the Search method:

class BinarySearchTree : BinaryTree&lt;int&gt;
    {
        public BinarySearchTree() : base() { } 

        public Boolean Search(int value)
        {
            if (Value.CompareTo(value) == 0) return true;
            if ((Left is not null) &amp;&amp; (Value.CompareTo(value) &gt; 0)) { return Left.Search(value); }
            else if ((Right is not null) &amp;&amp; (Value.CompareTo(value) &gt; 0)) { return Right.Search(value); }
            else { return false; }
        }
    }

Left.Search (and analagously Right) won't work because Left isn't of type BinarySearchTree, but rather BinaryTree&lt;int&gt;. I know that you can solve this by overriding _left, _right, Left and Right in BinaryTree, but that seems redundant since the new code isn't doing anything functionally different. I also know that we could just move Search to BinaryTree in this case, but that isn't the point; in the case of more complicated methods that I only want to have in a derived class it seems like this kind of functionality should be available somewhere.

My initial idea was to try to set the type of _left and _right in the base class to whatever the derived class would be. It doesn't seem like that's possible though; I can get the type as string using GetType() but I can't define a property with that type.

答案1

得分: 0

首先,你可能应该使用组合而不是继承,即将 BinaryTree<int> 作为 BinarySearchTree 的字段或属性。组合通常使代码更容易使用和理解,因为你不必担心复杂的继承层次结构。

class BinarySearchTree 
{
    private BinaryTree<int> root;
}

然后,你可以使你的搜索方法接受一个节点作为输入参数,通常使用私有方法进行递归,以及一个使用根节点的公共方法:

class BinarySearchTree 
{
    private BinaryTree<int> root;
    public Boolean Search(int value) => Search(root, value);
    private Boolean Search(BinaryTree<int> node, int value)
    {
        // 进行搜索操作...
    }
}

然而,还有一些其他事情需要考虑修复。

递归技巧存在一些问题。如果树平衡不好,可能会导致堆栈溢出。我建议考虑改用迭代技巧。这对于二叉搜索方法来说应该相当简单。

使用 IComparer<T> 参数而不是 IComparable 约束,以及一个重载,如果用户没有明确提供 IComparer<T>,则使用 Comparer<T>.Default。这是在灵活性和易用性之间取得良好平衡的做法,也是大多数框架的模式。

使用函数式方法而不是基于类的方法。这通常使代码更容易重用,因为你不会受限于特定的树类型。一个签名可能如下所示:

public static bool BinarySearch<T>(T current, T value, Func<T, (T? Left, T? Right)> selector, IComparer<T> comparer)

如果你处理大型的基本类型树,通常更有用的是使用数组来描述树。对象会带来一些开销,如果你有大量的整数,这些开销会占主导地位。有时,使用数组和索引到数组可以在处理大量数据时更好,典型的示例可能是堆这种情况。

英文:

First of all, you should probably use composition instead of inheritance, i.e. make BinaryTree&lt;int&gt; a field or property of BinarySearchTree. Composition tend to make code easier to use and reason about since you don't have to worry about complex inheritance hierarchies.

class BinarySearchTree 
{
    private BinaryTree&lt;int&gt; root;
}

You can then make your search method take a node as input parameter, commonly using an private method for recursion, and a public method that uses the root:

class BinarySearchTree 
{
    private BinaryTree&lt;int&gt; root;
    public Boolean Search(int value) =&gt; Search(root, value);
    private Boolean Search(BinaryTree&lt;int&gt; node, int value)
    {
        ...
    }
}

However, there are some things other things I would consider fixing.

Recursive techniques have some problems. If the tree is poorly balanced it can lean to stack overflows. I would recommend thinking about using a iterative technique instead. That should be fairly simple for a binary search method.

Use a IComparer&lt;T&gt; parameter instead of IComparable restriction, and an overload that uses Comparer&lt;T&gt;.Default if the user does not specifically give you one. This is a good compromise between flexibility and ease of use, and is the pattern most of the framework uses.

Use a functional approach rather than a class based approach. That tend to make the code easier to reuse, since you are not tied to a specific tree type. A signature might for example look something like this:

public static bool BinarySearch&lt;T&gt;(T current, T value, Func&lt;T, (T? Left, T? Right)&gt; selector, IComparer&lt;T&gt; comparer)

If you are handling large trees of primitive types, it is often more useful to describe the tree using an array. Objects have some overhead, and if you have a very large amount of ints, that overhead will dominiate. Sometimes, using arrays and indexes to that array can be a better solution when dealing with lots of data, a typical example would be something like a heap.

huangapple
  • 本文由 发表于 2023年2月14日 21:37:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75448648.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定