修复二叉树删除函数中的指针(带有2个子节点的节点)

时间:2010-02-18 20:31:42

标签: c binary-tree children

在尝试从二进制树中删除节点时,如何修复相应的指针我该怎么办?我有一点麻烦。

我理解基本概念,我基本上只是修复指针...

所以,基本上,我的删除功能大部分已经完成,并且每个案例都已经有效(就我的大量测试而言,一切正常),我只错过了有2个孩子的案例节点。假设我们在处理该案例的else if内:

我将有2个指针,currPtr保存要删除的节点,prevPtr保存父节点。我还有一个名为fromLeft的变量,它定义currPtr父是否来自prevPtr上的左或右指针。然后,我调用另一个名为extractMin(Tree *t)的函数,该函数提取树中的最低值。如果我以currPtr->right为参数调用此函数,则currPtr的后继将从树中提取(函数将从树中删除它,修复指针并返回节点指针)。后继指针将保存在tempPtr

这是我树的结构:

typedef int TreeElement;

typedef struct sTree {
   TreeElement item;

   struct sTree *left;
   struct sTree *right;
} Tree;

提取功能的代码:

Tree *extractMin(Tree *tree) {
    Tree *prevPtr = NULL;
    Tree *currPtr = tree;

    while(currPtr->left) {
        prevPtr = currPtr;
        currPtr = currPtr->left;
    }

    if(prevPtr) prevPtr->left = currPtr->right;

    // inorder successor
    return currPtr;
}

上面的代码缺少这里的情况,树只有一个节点,根节点,它将无法正常工作,它也不会检查树是否有任何节点,但是,当树有效时有一些节点。

那么,如何在else if上修复节点删除的必要指针?另外,请记住,树节点上的leftright指针始终指向某处或NULL

顺便说一句,我想迭代这样做。

3 个答案:

答案 0 :(得分:3)

已更新:因此,您希望保留节点的顺序,方法是将节点替换为直接顺序的后继节点或前一节点。

我们假设下面的树是有序的。节点的顺序是:

H < D < I < B < J < E < K < A < F < M < C < N < G < O

您要删除包含两个子节点的节点(A)。你拉出节点的前序(K)或后继(F)子节点代替原始子节点。让我们选择继任者。这就是你如何找到它:你遍历C的左边孩子,直到找到一个没有左孩子的孩子;这是A的直接继承者。

       A
   B       C
 D   E   F   G
H I J K   M N O

因此F被拉起,并且未触及A的左子树。但是,现在M也应该被拉起来,成为C的左边孩子(这是在你的extractMin()中完成的。)

在所有重新安排后,你得到

       F
   B       C
 D   E   M   G
H I J K     N O

在代码中,这是我的解决方案。我向extractMin()添加了NULL检查以简化调用者代码,因此我不需要else if

已更新 extractMin()以涵盖tree没有子女时的情况。

Tree *extractMin(Tree *parent, Tree *tree) {
    if (!tree) return NULL;

    Tree *prevPtr = NULL;
    Tree *currPtr = tree;

    while(currPtr->left) {
        prevPtr = currPtr;
        currPtr = currPtr->left;
    }

    if (prevPtr) {
        prevPtr->left = currPtr->right;
    } else if (parent) {
        parent->right = currPtr->right;
    }

    // inorder successor
    return currPtr;
}

// prevPtr is the parent, currPtr is the node to be deleted
Tree *successor = extractMin(currPtr, currPtr->right);
successor->left = currPtr->left;
successor->right = currPtr->right;
if (fromLeft) {
    prevPtr->left = successor;
} else {
    prevPtr->right = successor;
}
// Now currPtr can be destroyed

请注意,此代码未经过测试,因此我不保证: - )

请注意,像这样重复删除可能会使树不平衡(也就是说,某些路径会比其他路径长得多)。二进制排序树以这种方式进行删除,但也使用重新平衡后保持树接近理想值(每个叶子处于同一级别,如上面的第一个示例中所示)。

答案 1 :(得分:1)

教科书的答案是用它最左边的后代替换有问题的节点。

                6
         3             8
     2      4      7      9
   1          5             10

如果我们要删除3,我们可以将其替换为4,然后将5拉入4中的孔。我们总是可以这样做,它将保留有序遍历。

行。看着你的代码,这就是你想要的:

//in your function
    else if (/*has 2 nodes*/) {
        currPtr->item = extractMin(currPtr->right, &(currPtr->right))->item;
    }

Tree *extractMin(Tree *tree, Tree ** back) {
    Tree *currPtr = tree;

    while(currPtr->left) {
        back    = &(currPtr->left);
        currPtr = currPtr->left;
    }

    *back = currPtr->right;

    // inorder successor
    return currPtr;
}

**参数允许我们处理我们使用已删除节点的直接右子:

的情况
     3   <--deleting this node
   /   \   <--back points to this edge.
  2     4   <--extractMin is called on this node and returns this node,
         \
          5   <-- (*back) now points to this node. 

在2个案例中考虑新的ExtractMin。

  1. 在第一种情况下,我们至少进行一次循环。如果我们在代码中保留了prevPtr,我们会看到在循环之后back == &(prevPtr->left);(例如修改* back将修改prevPtr-&gt; left)。所以它与上面的代码相同。
  2. 在第二种情况下,我们不会经历循环。在这种情况下,back指向我们无法以任何其他方式获得的链接(除非我们修改extractMin以更高一级开始)。
  3. 考虑它的另一种方法是(* back)总是指向currPtr(花点时间检查一下),所以如果我们要删除currPtr,则返回指向我们需要修改的边缘。

答案 2 :(得分:0)

如果您对此很认真,请查看AVL树:

http://en.wikipedia.org/wiki/AVL_tree

它们可能很难实现,但由于您在插入和删除时执行的旋转,它们会保持平衡。