使用模板和模板继承难以将节点插入二叉搜索树

Posted

技术标签:

【中文标题】使用模板和模板继承难以将节点插入二叉搜索树【英文标题】:Difficulty inserting nodes to binary search tree using templates and template inheritance 【发布时间】:2017-09-04 21:15:04 【问题描述】:

我正在处理的是我创建了 3 个模板类。尽管我对 C++ 的了解有限,但我正在尝试为二叉树创建一个库。我已经使用结构在 C 中实现了二叉树,但这次我想利用 C++ 提供的面向对象编程,因此我为节点、二叉树和二叉搜索树创建了类。

如果有其他我没有注意到的错误,我会更自在地显示所有代码。所以就在这里。 (注:使用 C++11)

node_class.h

template <typename key_type, typename value_type>
class node_class 
public:
    node_class(key_type key, value_type value) 
        SetKey(key);
        SetValue(value);
        SetLeft(nullptr);
        SetRight(nullptr);
    
    void SetKey(key_type key) 
        this->key = key;
    
    void SetValue(value_type value) 
        this->value = value;
    
    void SetLeft(node_class <key_type, value_type> *left) 
        this->left = left;
    
    void SetRight(node_class <key_type, value_type> *right) 
        this->right = right;
    
    key_type GetKey() 
        return this->key;
    
    value_type GetValue() 
        return this->value;
    
    node_class <key_type, value_type> *GetLeft() 
        return this->left;
    
    node_class <key_type, value_type> *GetRight() 
        return this->right;
    
private:
    key_type key;
    value_type value;
    node_class <key_type, value_type> *left;
    node_class <key_type, value_type> *right;
;

binary_tree_class.h

template <typename key_type, typename value_type>
class binary_tree_class 
public:
    binary_tree_class() 
        SetRoot(nullptr);
    
    // ...
protected:
    void SetRoot(node_class <key_type, value_type> *root) 
        this->root = root;
    
    node_class <key_type, value_type> *GetRoot() 
        return this->root;
    
private:
    node_class <key_type, value_type> *root;
    // ...
;

binary_search_tree_class.h

template <typename key_type, typename value_type>
class binary_search_tree_class : public binary_tree_class <key_type, value_type> 
public:
    binary_search_tree_class() 

    
    void Insert(key_type key, value_type value) 
        Insert(key, value, this->GetRoot());
    
    void Insert(key_type key, value_type value, node_class <key_type, value_type> *node) 
        if (node == nullptr) 
            node = new node_class <key_type, value_type> (key, value);
        
        if (key > node->GetKey()) 
            Insert(key, value, node->GetRight());
         else if (key < node->GetKey()) 
            Insert(key, value, node->GetLeft());
        
    
    // ...
;

就插入函数而言,我读到我必须通过引用指针来传递节点参数,以便对树进行更改(如果我错了,请纠正我)。话虽如此,我必须从此更改函数原型

void Insert(key_type key, value_type value, node_class <key_type, value_type> *node)

进入这个

void Insert(key_type key, value_type value, node_class <key_type, value_type> *&node)

在我这样做之后,我完全无法弄清楚出了什么问题,所以我仍然遇到来自 g++ 的以下错误

In file included from main.cpp:1:0:
binary_search_tree_class.h: In instantiation of ‘void binary_search_tree_class<key_type, value_type>::Insert(key_type, value_type) [with key_type = int; value_type = int]’:
main.cpp:5:31:   required from here
binary_search_tree_class.h:11:9: error: invalid initialization of non-const reference of type ‘node_class<int, int>*&’ from an rvalue of type ‘node_class<int, int>*’
   Insert(key, value, this->GetRoot());
         ^
binary_search_tree_class.h:13:7: note:   initializing argument 3 of ‘void binary_search_tree_class<key_type, value_type>::Insert(key_type, value_type, node_class<key_type, value_type>*&) [with key_type = int; value_type = int]’
  void Insert(key_type key, value_type value, node_class <key_type, value_type> *&node) 

在上面的文件中,我无法编写包含指令,因为 # 符号导致缩进出现问题,但我想你明白了。

【问题讨论】:

How come a non-const reference cannot bind to a temporary object?的可能重复 @tobi303 我会检查一下;) 您的GetRoot 按值返回指针,即,如果您调用this-&gt;GetRoot(),您会得到一个临时值,然后将其传递给Insert,即使这样也行不通,它不会做您想要的,因为它只会改变那个临时指针的值,而不是root成员 辛苦了!操作。一旦您克服了编译器错误,我强烈建议您将此代码发送至codereview.stackexchange.com,因为您的代码中有大量内容可以改进。完成后,您可以在此处分享 cmets 中的链接。 你可以创建一个GetRootRef来返回对指针的引用,或者直接将root设置为protected 【参考方案1】:

问题

问题从方法开始:

node_class<>* binary_search_tree<>::GetRoot();

它返回变量成员rootcopy指针。

换句话说,当您调用方法GetRoot() 时,您将获得root 节点的地址位置,并可能将该地址保存到另一个指针变量中。

只是给你一个想法。让我们假设内存中某处有根节点:

 0x01 [...]
 0x02 [RootNode]
 0x03 [...]

您的二叉树将保留根节点的地址并将其存储在一个指针中(无论如何它是一个变量)。

布局内存将类似于:

0x01 [...]
0x02 [RootNode]
0x03 [...]
0x04 [binary_tree::root = 0x02]

假设我们调用以获取根地址:GetRoot() 它实际上将返回RootNode 的地址,并且不是对成员变量binary_tree:root 的引用 指向RootNode

所以当你调用GetRoot() 时:

void foo() 
  auto p = binary_tree.GetRoot();

布局内存将类似于:

0x01 [...]
0x02 [RootNode]
0x03 [...]
0x04 [binary_tree::root = 0x02]
0x05 [...]
0x06 [p = 0x2]

正如您在方案中看到的,proot(变量成员)的副本,但它们实际上是两个不同指针实际上指向同一个位置内存。

因此,如果您将 p 作为 reference 传递给修改它的函数,则 p 将被修改,但不会修改成员变量 ::root

该方案和代码是相当示意性的,但我希望它们能帮助您了解情况。 GetNode() 就像复制成员变量而不返回对它的引用。

您的编译错误源于您实际上并未将返回的复制地址保存到另一个指针中,而是直接将其作为参数传递。因此创建了一个临时表达式并将其传递给方法Insert。但是,临时表达式无法通过内存中的位置来识别,因此您无法从中获取引用,这就是您的编译器说不能从临时值中获取引用的原因。


如何解决?

有几种方法可以解决这个问题。

我认为你可以改变你的类设计:只要使成员变量 protected 以便被访问。这样,您可以获得“真实”指针,而不是复制地址值。


简单的建议

这可能是个人的,但我认为带有指针引用的符号非常难以理解。 如果你的函数(或方法)需要修改指针的地址,只需使用:void foo(node_class**)(指向指针的指针)即可。

_class 添加到每个名称类是非常多余的,并且会使代码更加冗长。类名应该只是node

【讨论】:

首先感谢您向我解释问题所付出的努力。就类名而言,我使用的是欧洲航天局 C/C++ 标准。我现在明白了这个问题,但我尝试了指向指针解决方案的指针,但我无法正确调用函数。然后我继续使用您提供的第二个解决方案,因此我摆脱了 GetRoot() 函数并将根指针更改为受保护但它也不起作用:/ @xorz57 您应该将*root 作为受保护的成员变量并删除GetRoot。在派生方法Insert 中,您应该将root 作为第三个参数传递(参数仍然必须是node_class*&amp;,因为您需要更改指针的地址)。

以上是关于使用模板和模板继承难以将节点插入二叉搜索树的主要内容,如果未能解决你的问题,请参考以下文章

模板非旋转Treap

LeetCode-树二叉搜索树与双向链表

手撕STL二叉搜索树

手撕STL二叉搜索树

C++二叉树进阶

C++二叉树进阶(二叉搜索树,KV模型)