简体   繁体   English

递归地将元素插入二叉树

[英]Recursively Insert Element Into A Binary Tree

So I finished my List exercise and went ahead with Binary Trees. 所以我完成了List练习并继续使用Binary Trees。 My code thus far: 我的代码到目前为止:

Tree.h tree.h中

#include "Node.h"

class Tree
{
private:
    int mCount;
    Node *root;

public:
    Tree();
    ~Tree();

    void insert(int, Node *);
};

Tree.cpp Tree.cpp

void Tree::insert(int data, Node *node)
{
    if( root == 0 )
    {
        Node *temp = new Node;
        temp->setData(100);
        temp->setRight(0);
        temp->setLeft(0);
        root = temp;
    }
    else
    {
        if( data > root->getData() )
            return insert( data, root->getRight() );
        else
            return insert( data, root->getLeft() );
    }
}

main.cpp main.cpp中

int main(int argc, char** argv)
{
    Tree *tree = new Tree;
    tree->insert( 100, 0 );

    std::cin.get();
    return 0;
}

I hope this is sufficient code. 我希望这是足够的代码。 Node and Tree are two separate classes. NodeTree是两个独立的类。 I'm having difficulties wrapping my head around the recursion. 我在绕递归时遇到困难。

I have Node *root defined in my Tree class to have a root node at the top of the tree. 我在Tree类中定义了Node *root在树的顶部有一个根节点。 However, the way I see it, when I call tree->insert insert in main, I don't have to specify any node. 但是,我看到它的方式,当我在main中调用tree->insert insert时,我不必指定任何节点。 The root from the Tree class will do all the looking. Tree类的根将完成所有工作。 However, when I'm in the code and need to recur, then I am suddenly a parameter short, as shown above. 但是,当我在代码中并且需要重复时,我突然变成一个参数,如上所示。

My solution was to just place the parameter Node *node in the argument list for insert() anyway and call it with a 0 from main. 我的解决方案是将参数Node *node放在insert()的参数列表中,然后用main中的0调用它。 I would also need to call tree->display(0); 我还需要调用tree->display(0); as parameter for Node *node as well. 作为Node *node参数。

This seems hackish. 这看起来很骇人听闻。 Am I missing something obvious? 我错过了一些明显的东西吗

A few points: 几点:

First, don't use Node** . 首先,不要使用Node** That err "uglifies" your code. 那错误“丑化”了你的代码。 Use Node*& instead (see answers here ) if you really need to. 如果确实需要,请使用Node*& (请参阅此处的答案)。

Second, you do not need a recursive call (unless you want to use one). 其次,您不需要递归调用(除非您想使用一个)。

Non-recursive insert method: 非递归插入方法:

void Tree::insert(int data)
{
    if(!root)
    {
         root = new Node(data);  // Node constructor should receive
                                 // the data value and init internal value from it
                                 // it should also set left and right pointers to 0
         return;
    }

    Node* insertIterator = root;
    Node* parent = 0;

    while(insertIterator)
    {
         parent = insertIterator;
         insertIterator = data < insertIterator->getData() 
             ? insertIterator->getLeft()
             : insertIterator->getRight();
    }

    if(data < parent->getData())
         parent->setLeft( new Node(data) );
    else
         parent->setRight( new Node(data) );
}

If you do use a recursive method, use a recursive method that finds the insertion point, instead of a recursive method that performs the insertion. 如果使用递归方法,使用该发现的,而不是执行插入递归方法将插入点,递归方法。 Basically, replace the while loop in the code above with a separate method ( FindInsertionPoint in my code below): 基本上,用一个单独的方法替换上面代码中的while循环(我的代码中的FindInsertionPoint ):

Node* Tree::FindInsertionPoint(int data, Node * parent) // this should be private
{
    Node* insertPoint = data < parent.getData()
        ? parent->getLeft() 
        : parent->getRight();

    return insertPoint
        ? FindInsertionPoint(data, insertPoint)
        : parent;
}

void Tree::Insert(int data)  // this should be public
{
    if(!root)
    {
        root = new Node(data);
        return;
    }

    Node* parent = FindInsertionPoint(data, root);
    if(data < parent.getData())
        parent->setLeft(new Node(data)); // see comment on Node constructor above
    else
        parent->setRight(new Node(data)); // see comment on Node constructor above
}

Edit : 编辑

I'm having difficulties wrapping my head around the recursion. 我在绕递归时遇到困难。

Look at it like this: to find the insertion point , you know that you need to insert as a child of the left or the right subnode . 看看它是这样的:要找到插入点 ,您知道需要插入左侧或右侧子节点的子节点 To insert to the left, you need to insert as a child of the left or the right subnode of the left child of the current node. 要插入左侧, 您需要插入当前节点的左子节点的左子节点或子子节点的子节点。 That is, if you insert to the left, call the find the insertion point part for the left child; 也就是说,如果你向左边插入,则调用找到左边孩子的插入点部分; otherwise, call the find the insertion point for the right subnode. 否则,调用查找右子节点的插入点

What you need to do to define a recursive algorithm: 您需要做什么来定义递归算法:

  • identify the algorithm as it applies to a part of your data (in this case, you need to insert as a child of the left or the right subnode ). 识别适用于部分数据的算法(在这种情况下, 您需要插入左侧或右侧子节点的子节点 )。

  • identify the stopping condition (when is the algorithm stopping?). 识别停止条件(算法何时停止?)。 If you do not, you get infinite recursion and a stackoverflow error :). 如果你不这样做,你会得到无限递归和stackoverflow错误:)。

  • identify the variable part of the algorithm (this should tell you what parameters your recursive function will have). 识别算法的可变部分(这应该告诉你递归函数将具有哪些参数)。

You are currently not using the node parameter at all in Tree::insert , which effectively means that it will recurse infinitely if you already have a root node. 您当前在Tree::insert根本没有使用node参数,这实际上意味着如果您已经拥有根节点,它将无限递归。

The best solution would be to define a public insert method without node parameter, which calls another, private insert method with the root parameter, which in turn recursively calls itself. 最好的解决方案是定义一个没有node参数的公共插入方法,该方法使用root参数调用另一个私有insert方法,而root参数又以递归方式调用自身。 This way your API is clean, not allowing its clients to insert elements directly (and incorrectly) into subtrees. 这样,您的API就是干净的,不允许客户端直接(和不正确地)将元素插入到子树中。

Note that the parameter itself must be changed to Node** Node*& , since at the point of insertion, you want to modify the pointer in the parent node. 请注意,参数本身必须更改为 Node** Node*& ,因为在插入时,您希望修改父节点中的指针。

[Update] Also it would be recommended to add a constructor to Node which takes the data value and initializes its left and right pointers to 0. This simplifies the caller code noticeably. [更新]此外,建议向Node添加一个构造函数,该构造函数获取数据值并将其左右指针初始化为0.这样可以显着简化调用者代码。 [/Update] [/更新]

So the final result would look something like this: 所以最终结果看起来像这样:

void Tree::insert(int data)
{
    return insert( data, root );
}

void Tree::insert(int data, Node *&node)
{
    if( node == 0 )
    {
        node = new Node(data);
    }
    else
    {
        if( data > node->getData() )
            return insert( data, node->getRight() );
        else
            return insert( data, node->getLeft() );
    }
}

Péter gave a perfectly nice example. 佩特给出了一个非常好的例子。 Only thing I see is that temp->setData(100) should be temp->setData(data); 我唯一看到的是temp-> setData(100)应该是temp-> setData(data);

However, I want to focus on this part: 但是,我想关注这一部分:

I'm having difficulties wrapping my head around the recursion. 我在绕递归时遇到困难。

When you're first introduced to recursion, it can be a little hard to get your mind working that way. 当你第一次介绍递归时,让你的思维以这种方式工作可能有点困难。 We tend to want to think of algorithms as a whole with an ordered list of sequential steps. 我们倾向于想要将算法作为一个整体来考虑顺序步骤的有序列表。 The best way to get your mind thinking about it is to draw this out. 让你的思维考虑它的最好方法是把它画出来。 Do it enough and it'll become second nature. 做得足够,它将成为第二天性。

Let's consider Péter's example (ignoring the one slight omission of correcting the setData line). 让我们考虑一下Péter的例子(忽略纠正setData行的一点点遗漏)。 We only have two very simple situations: 我们只有两种非常简单的情况:

1) We're calling insert on a node that exists. 1) 我们在存在的节点上调用insert。 In this case, we compare the value we are inserting to the value of the node. 在这种情况下,我们将插入的值与节点的值进行比较。 If it's greater than the node value, we insert to the right child, otherwise to the left. 如果它大于节点值,我们插入到右边的子节点,否则插入到左边。 Now you just repeat the process on the child to which you are inserting. 现在,您只需对要插入的子项重复此过程。

2) We're calling insert on a node that doesn't exist. 2) 我们在不存在的节点上调用insert。 In this case, we create the node and set its value to the insertion value. 在这种情况下,我们创建节点并将其值设置为插入值。 The end: recursion is finished. 结束:递归完成。 This is called the base case or general solution because it is where we stop. 这被称为基本案例或一般解决方案,因为它是我们停止的地方。

That's it - it's actually quite simple. 就是这样 - 它实际上非常简单。 The trick is not to think about what's happening to the tree as a whole, but just one node at a time, and consider all the cases with that one given node. 诀窍不是要考虑整个树上发生了什么,而是一次只考虑一个节点,并考虑具有该节点的所有情况。 When you can get your mind thinking this way, we find there's only two cases (three if you want to be pedantic, but two general situations). 当你能够以这种方式思考时,我们发现只有两种情况(如果你想要迂腐,则有三种情况,但有两种情况)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM