在C中为二叉搜索树实现删除功能

时间:2012-12-28 16:40:31

标签: c binary-search-tree

我一直在尝试在C中实现一个删除二叉树中的节点的函数,该节点应该(理论上)处理所有三种情况,即:

  • 节点是叶子
  • 节点有一个孩子
  • Node有两个孩子

有没有办法处理整个删除功能而不分别检查每个案例?作为下面提到的评论者,我确实检查了很多案例,也许通过检查一个基本案例可以递归地解决整个问题。

我特别感兴趣的是我删除树中具有父节点的节点,并且它本身是两个子节点的父节点。

以下两个答案都很有用,但我不认为它们完全解决了这个问题。

这就是我所拥有的:

typedef struct Node
{
    int key;
    int data;
    struct Node *left;
    struct Node *right;
    struct Node *parent;
} Node;

/* functions that take care of inserting and finding a node and also traversing and freeing the tree */
...

void delete(Node *root, int key)
{
    Node *target = find(root, key); // find will return the node to be deleted

    Node *parent = target->parent; // parent of node to be deleted

    // no children
    if (target->left == NULL && target->right == NULL)
    {
        // is it a right child
        if (target->key > parent->key)
            parent->right = NULL;
        // must be a left child
        else
            parent->left = NULL;

        free(target);
    }

    // one child
    else if ((target->left == NULL && target->right != NULL) || (target->left != NULL && target->right == NULL))
    {
        // here we swap the target and the child of that target, then delete the target
        Node *child = (target->left == NULL) ? target->right : target->left;
        child->parent = parent;
        if (parent->left == target) parent->left = child;
        else if (parent->right == target) parent->right = child;
        free(target);
    }
    // two children
      else
    {

        // find the largest node in the left subtree, this will be the node
        // that will take the place of the node to be deleted
        Node *toBeRepl = max(target->left);

        // assign the data of the second largest node
        target->key   = toBeRepl->key;
        target->data  = toBeRepl->data;

        // if new node immediately to the left of target 
        if (toBeRepl == target->left)
        {   
            target->left  = toBeRepl->left;
            Node *newLeft = target->left;
            if (newLeft != NULL) newLeft->parent = target;
        }
        else
        {
            delete(target->left, toBeRepl->key);
            // Node *replParent = toBeRepl->parent;
            // replParent->right = NULL;
        }
}

非常感谢您的反馈。

编辑:为了澄清,我正在尝试删除特定节点而不触及其子树(如果有的话)。它们应该保持完整(我通过交换要删除的节点的值和(取决于具体情况)其子节点之一来处理)。

编辑:我用作以下维基百科文章的参考: http://en.wikipedia.org/wiki/Binary_search_tree#Deletion
这是我在两个孩子的情况下交换节点值的想法,特别是引用:

  

调用要删除的节点N.不要删除N.而是选择其中之一   它的有序后继节点或其有序前导节点R.   将N的值替换为R的值,然后删除R.

对于上述情况,C ++中有一些有趣的代码,但是我不确定交换是如何发生的:

else    //2 children
{
        temp = ptr->RightChild;
        Node<T> *parent = nullptr;

        while(temp->LeftChild!=nullptr)
        {
                parent = temp;
                temp = temp->LeftChild;
        }
        ptr->data = temp->data;
        if (parent!=nullptr)
                Delete(temp,temp->data);
        else
                Delete(ptr->rightChild,ptr->RightChild->data);
}

有人可以解释一下该部分的内容吗?我假设递归与用户评论'在这里类似。

3 个答案:

答案 0 :(得分:1)

我会使用递归来做,假设你在树的末尾有null,找到null将是'返回'或返回条件。

一种可能的算法是:

Node* delete(Node *aNode){
  if(aNode->right != NULL)
     delete(aNode->right);
  if(aNode->left != NULL)
     delete(aNode->left);
  //Here you're sure that the actual node is the last one
  //So free it! 
  free(aNode);
  //and, for the father to know that you're now empty, must return null
  return NULL;

}

肯定会有一些错误,但这是主要的想法。 这个实现是dfs之类的。 希望这会有所帮助。

[编辑]节点* aNode已修复。忘了明星,我的坏。

答案 1 :(得分:1)

我没有在代码中看到任何“不雅”,这样的格式和注释代码很难得到。但是,是的,您可以将删除函数中的if-else结构减少到只有一种情况。如果你看一下删除操作最抽象的概念,你会注意到所有的情况基本上归结为最后一种情况(删除有两个孩子的节点)。

你只需在其中添加几行。就像在toBeRepl = max(left-sub-tree)之后,检查它是否为NULL,如果是,那么toBeRepl = min(right-sub-tree)。

所以,案例1(没有孩子):假设您的max()方法正确实现,它将返回NULL作为左子树中最右边的元素,右侧子树上的min()也是如此。用toBeRepl替换你的目标,你将删除你的节点。

案例2(一个孩子):如果max()确实返回NULL,则min()将不会,反之亦然。所以你将拥有一个非NULL toBeRepl。再次用这个新的toBeRepl替换你的目标,你就完成了。

案例3(两个孩子):与案例2相同,只有您可以确保max()不会返回NULL

因此,您的整个delete()函数将归结为最后一个else语句(稍作更改)。有点像:

Node *toBeRepl = max(target->left);
if toBeRepl is NULL
{
  toBeRepl = min(target->right);
}

if toBeRepl is not NULL
{
  target->key = tobeRepl->key;
  target->data = toBeRepl->data;

  deallocate(toBeRepl); // deallocate would be a free(ptr) followed by setting ptr to NULL
}
else
{
  deallocate(target);
}

答案 2 :(得分:0)

我很久以前就完成了这项工作,我认为为来这里遇到同样问题的人添加一个示例答案会更好(考虑到这个问题积累了400多个观点):

/* two children */
else
{
    /* find the largest node in the left subtree (the source), this will be the node
     * that will take the place of the node to be deleted */
    Node* source = max(target->left);

    /* assign the data of that node to the one we originally intended to delete */
    target->key   = source->key;
    target->data  = source->data;

    /* delete the source */
    delete(target->left, source->key);
}

维基百科有excellent article启发了这段代码。