英文:
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,_right,Left和Right来解决这个问题,但这似乎是多余的,因为新代码在功能上没有任何不同。我也知道我们可以在这种情况下将Search移动到BinaryTree中,但这不是重点;对于我只想在派生类中拥有的更复杂方法来说,似乎应该在某个地方提供这种功能。
我最初的想法是尝试将基类中的_left和_right的类型设置为派生类的类型。但似乎不可能;我可以使用GetType()来获取类型的字符串,但无法使用该类型定义属性。
英文:
I have a class BinaryTree<T> that looks like this:
 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()
        { ... }
    }
And a dervied class BinarySearchTree that implements the Search method:
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 (and analagously Right) won't work because Left isn't of type BinarySearchTree, but rather BinaryTree<int>. 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<int> 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<int> 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<int> root;
    public Boolean Search(int value) => Search(root, value);
    private Boolean Search(BinaryTree<int> 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<T> parameter instead of IComparable restriction, and an overload that uses Comparer<T>.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<T>(T current, T value, Func<T, (T? Left, T? Right)> selector, IComparer<T> 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论