递归地将元素插入二叉树

时间:2010-06-25 08:38:57

标签: c++ binary-tree

所以我完成了List练习并继续使用Binary Trees。到目前为止我的代码:

tree.h中

#include "Node.h"

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

public:
    Tree();
    ~Tree();

    void insert(int, Node *);
};

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

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

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

我希望这是足够的代码。 NodeTree是两个单独的类。我在绕递归时遇到困难。

我在Tree类中定义了Node *root,在树的顶部有一个根节点。但是,我看到它的方式,当我在main中调用tree->insert insert时,我不必指定任何节点。 Tree类的根将完成所有工作。但是,当我在代码中并且需要重复时,我突然变成了一个参数,如上所示。

我的解决方案是将参数Node *node放在insert()的参数列表中,然后从main调用0。我还需要将tree->display(0);作为Node *node的参数调用。

这似乎是个hackish。我错过了一些明显的东西吗

3 个答案:

答案 0 :(得分:6)

几点:

首先,不要使用Node**。那错误“丑化”了你的代码。如果您确实需要,请使用Node*&(请参阅答案here)。

其次,您不需要递归调用(除非您想使用递归调用)。

非递归插入方法:

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) );
}

如果你使用递归方法,请使用找到插入点的递归方法,而不是执行插入的递归方法。基本上,使用单独的方法替换上面代码中的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
}

修改

  

我在缠绕我的时候遇到了困难   转发递归。

如下所示:要找到插入点,您知道需要插入左侧或右侧子节点的子级。要插入左侧,需要插入当前节点左子节点的左子节点的子节点。也就是说,如果您向左侧插入,请为左侧孩子调用找到插入点部分;否则,请为正确的子节点调用找到插入点

定义递归算法需要做什么:

  • 识别适用于部分数据的算法(在这种情况下,您需要作为左侧或右侧子节点的子项插入)。

  • 识别停止条件(算法何时停止?)。如果不这样做,则会得到无限递归和 stackoverflow 错误:)。

  • 识别算法的可变部分(这应该告诉你递归函数将具有哪些参数)。

答案 1 :(得分:4)

您目前在node中根本没有使用Tree::insert参数,这实际上意味着如果您已经拥有根节点,它将无限递归。

最好的解决方案是定义一个没有node参数的公共插入方法,该方法使用root参数调用另一个私有insert方法,而root参数又以递归方式调用自身。通过这种方式,您的API是干净的,不允许其客户端直接(和不正确地)将元素插入到子树中。

请注意,参数本身必须更改为 Node** Node*&,因为在插入时,您希望修改父节点中的指针。

[更新] 此外,还建议向Node添加一个构造函数,该构造函数获取数据值并将其左右指针初始化为0.这样可以显着简化调用者代码。的 [/更新]

所以最终结果看起来像这样:

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() );
    }
}

答案 2 :(得分:1)

Péter给出了一个非常好的例子。我唯一看到的是temp-&gt; setData(100)应该是temp-&gt; setData(data);

但是,我想关注这一部分:

  

我在缠绕我的时候遇到了困难   转发递归。

当你第一次介绍递归时,让你的思维以这种方式工作可能有点困难。我们倾向于想要将算法作为一个整体来考虑顺序步骤的有序列表。让你的思维考虑它的最好方法是把它画出来。做得足够,它将成为第二天性。

让我们考虑一下Péter的例子(忽略纠正setData行的一点遗漏)。我们只有两种非常简单的情况:

1)我们在存在的节点上调用insert。在这种情况下,我们将插入的值与节点的值进行比较。如果它大于节点值,我们插入到右边的子节点,否则插入到左边。现在,您只需对要插入的子项重复此过程。

2)我们在不存在的节点上调用insert。在这种情况下,我们创建节点并将其值设置为插入值。结束:递归完成。这被称为基本案例或一般解决方案,因为它是我们停止的地方。

就是这样 - 它实际上非常简单。诀窍不是要考虑整个树上发生了什么,而是一次只考虑一个节点,并考虑具有该节点的所有情况。当你能够以这种方式思考时,我们发现只有两种情况(如果你想要迂腐三种,那么就是两种情况)。