【问题标题】:Delete a Node from C++ Binary Search Tree (class not struct)从 C++ 二叉搜索树中删除一个节点(类不是结构)
【发布时间】:2017-11-18 12:23:29
【问题描述】:

出于学术目的,我正在尝试用 C++ 管理 BST。

除了DeleteNode 函数之外,我在任何地方都没有问题 我选择使用class 而不是struct 来实现这个数据结构。

问题是,我不知道如何使删除功能正常工作,我经常收到0xDDDDDDDDD 错误我的调试器说,有时我可以删除节点,有时我的程序崩溃。

我认为这可能是指针问题,但我无法弄清楚我做错了什么。

这是我的删除节点功能,我遇到了严重的麻烦:

编辑:no-son 删除案例完美,我生气的是 one-son-case 删除。

 //function that delete a selected node
    void DeleteNode(TreeNode* root,int key) {
        /*we got three case here:*/


        //until we find the right node with value in the tree
        if (root->getValue() != key && root != nullptr) {
            if (root->getValue() > key) {
                DeleteNode(root->Left, key);
            }
            else if (root->getValue() < key) {
                DeleteNode(root->Right, key);
            }
        }
        else { //when we found the right node, then operate 
               /* THIS WORKS PERFECTLY! */

            //first case: our node got no right and left son
            if (!root->Left && !root->Right) {

                TreeNode* tmp = root->Father; 

                if (tmp->Left == root) { //if the son is a left son
                    tmp->Left = nullptr;
                    delete root;
                }
                else if (tmp->Right == root) { //if the son is a right son
                    tmp->Right = nullptr;
                    delete root;
                }

            }
            //second case: our node got a left but no right son
            /* THIS ONE DOESN'T WORK. */
            else if (!root->Right) { 
                TreeNode *tmp = root;
                root = root->Left; //new root is the left son of the root
                root->Father = tmp->Father; //linking the father to the new son
                tmp->Father->Left = root; //linking the son to the new father

                delete tmp;                     

                std::cout << "Erased!" << std::endl;
            }
            else if (!root->Left) {
                TreeNode *tmp = root;
                root = root->Right; //new root is the right son of the root
                root->Father = tmp->Father; //linking the father to the new son
                tmp->Father->Right = root; //linking the son to the new father

                delete tmp;

                std::cout << "Erased!" << std::endl;

            }
        }


        }

我尝试了很多组合,但每次结果都一样:它在InOrder 显示函数的第一次出现时崩溃。 (如果没有,该函数只会删除第一个节点,然后在我尝试删除新节点时崩溃。)

这是一个简单的主要内容,我正在尝试执行删除操作:

int main()
{

    TreeNode root;

    root.insertNode(&root,50);
    root.insertNode(&root,30);
    root.insertNode(&root,20);
    root.insertNode(&root,40);
    root.insertNode(&root,70);
    root.insertNode(&root,60);
    root.insertNode(&root,80);

    for (int i = 0; i < 5; i++) {
        int n;
        cin >> n;

        root.DeleteNode(&root, n);

        cout << "In-Order: "; root.inOrder(&root);
        cout << endl;
        cout << "Pre-Order: "; root.preOrder(&root);
        cout << endl;
        cout << "Post-Order: "; root.postOrder(&root);
        cout << endl;
    }

}

这是我的完整BST代码(除了我之前提交的删除,只是为了更完整地理解我的代码)

    class TreeNode {
private:
    int value;
    TreeNode* Left;
    TreeNode* Right;
    TreeNode* Father;

public:

    //constructor
    TreeNode() {
        this->Right = nullptr;
        this->Left = nullptr;
        this->Father = nullptr;
    }

    TreeNode(int value) {
        this->value = value;
        this->Right = nullptr;
        this->Left = nullptr;
        this->Father = nullptr;
    }

    //functions
    int getValue() { return value; }
    void setValue(int value) { this->value = value; }


    //function to create a new node and insert a value into it
    TreeNode* insertNode(TreeNode* root, int value) {

        if (root->getValue() == NULL) {
            root->setValue(value);
            root->Father = nullptr;
        }

        else {
            if (value > root->getValue()) {
                if (root->Right) {
                    insertNode(root->Right, value);
                }
                else
                    root->Right = new TreeNode(value);
                    root->Right->Father = root;
            }
            else if (value < root->getValue()) {
                if (root->Left) {
                    insertNode(root->Left, value);
                }
                else
                    root->Left = new TreeNode(value);
                    root->Left->Father = root;
            }

        }
        return root;
    }

    //function to search a value into a BST
    TreeNode* SearchNode(TreeNode* root, int key) {

        if (root->getValue() == key) {
            return root;
        }
        else if (root->getValue() < key) {
            if (root->Right) {
                SearchNode(root->Right, key);
            }
            else return nullptr;
        }
        else if (root->getValue() > key) {
            if (root->Left) {
                SearchNode(root->Left, key);
            }
            else return nullptr;
        }
    }

    //function that return the height of the tree 
    int TreeHeigth(TreeNode* root) {

        int heigth;

        if (root == nullptr) {
            return 0;
        }
        else {
            return heigth = 1 + max(TreeHeigth(root->Left), TreeHeigth(root->Right));
        }
    }

    //function that returns the number of the nodes
    int CountTreeNode(TreeNode* root) {
        if (root == nullptr) {
            return 0;
        }
        else {
            return CountTreeNode(root->Left) + CountTreeNode(root->Right) + 1;
        }
    }

    //function that returns the minimum values into the tree
    TreeNode* MinimumNode(TreeNode* root) {
        if (root == nullptr) {
            return nullptr;
        }

        while (root->Left != nullptr) {
            root = root->Left;
        }

        return root;
    }

    //function that returns the maximum value into the tree  
    TreeNode* MaximumNode(TreeNode* root) {
        if (root == nullptr) {
            return nullptr;
        }

        while (root->Right != nullptr) {
            root = root->Right;
        }

        return root;
    }

    //function that returns a successor of a given nodeb
    TreeNode* SuccessorNode(TreeNode* node) {

        //first case: our node got a rigth subtree:
        if (node->Right != nullptr) {
            return MinimumNode(node->Right); 
        }

        //second case: our node doesnt got a right subtree: lets get 
        //upper in the tree until our node isn't a left child.

        TreeNode* Ancestor = node->Father;

        while (Ancestor != nullptr && node == Ancestor->Right) {
            node = Ancestor;
            Ancestor = Ancestor->Father;
        }

    }

    //function tht returns a predecessor of a given node
    TreeNode* PredecessorNode(TreeNode* node) {

        //first case: (inverse to successor) our node got a left subtree:
        if (node->Left != nullptr) {
            return MaximumNode(node->Left);
        }

        TreeNode* Ancestor;

            if (node->Father == nullptr)
                return nullptr;
            else 
                Ancestor = node->Father;

        while (Ancestor != nullptr && node == Ancestor->Left) {
            node = Ancestor;
            Ancestor = Ancestor->Father;
        }

        return Ancestor;
    }



    //function that prints information about nodes
    void InfoNode(TreeNode *root) {

        root != nullptr ? std::cout << "Nodo corrente: " << root->getValue() << std::endl
            : std::cout << "Nodo corrente: " << "NULL" << std::endl;

        root->Father != nullptr? std::cout << "Padre: " << root->Father->getValue() << std::endl
            : std::cout << "Padre: " << "NULL" << std::endl;

        root->Left != nullptr ? std::cout << "Figlio SX: " << root->Left->getValue() << std::endl
            : std::cout << "Figlio SX: " << "NULL" << std::endl;

        root->Right!= nullptr ? std::cout << "Figlio DX: " << (root->Right)->getValue() << std::endl
            : std::cout << "Figlio DX: " << "NULL" << std::endl;
    }

    //visits of a tree
    void preOrder(TreeNode* root) {
        if (root != nullptr) {
            std::cout << root->getValue() << " ";
            preOrder(root->Left);
            preOrder(root->Right);
        }
    }

    void inOrder(TreeNode* root) {
        if (root != nullptr) {
            inOrder(root->Left);
            std::cout << root->getValue() << " ";
            inOrder(root->Right);
        }

    }

    void postOrder(TreeNode *root) {
        if (root != nullptr) {
            postOrder(root->Left);
            postOrder(root->Right);
            std::cout << root->getValue() << " ";
        }
    }


    //max between 2 numbers
    int max(int a, int b) {
        return a > b ? a : b;
    }


    };

还有我正在尝试处理的树的表示:

          50
       /     \
      30      70
     /  \    /  \
   20   40  60   80 

我哪里做错了?

【问题讨论】:

  • 旁注:类和结构之间没有根本区别。默认情况下,结构简单地具有所有成员public,而类具有它们private
  • 我也尝试将所有指向父亲、左右儿子的指针设置为public,但这根本没有帮助。
  • 另一个侧节点:这似乎是从二叉树中删除节点的代码,而不是二叉搜索树(需要在删除内部节点后重新排序节点)。
  • 你能解释清楚吗?
  • 答案有点太长了,不适合这里,但例如看看维基百科的Binary treeBinary search tree 页面。基本上,在删除父节点时,可能需要其中一个子节点代替它的位置,以使其保持为正确的二叉搜索树。

标签: c++ binary-search-tree


【解决方案1】:

看看这个条件:root-&gt;getValue() != key &amp;&amp; root != nullptr,这首先调用getValue,然后检查root是否具有合法价值。交换它们(root != nullptr &amp;&amp; root-&gt;getValue() != key)。

最后我认为您必须将最后一行更改为tmp-&gt;Father-&gt;Left = root; 才能解决崩溃问题。

TreeNode *tmp = root;
root = root->Right; //new root is the right son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Right = root; //linking the son to the new father

PS:也为对方做这个交换……

注意:这是真的,直到root 是他父亲的左孩子,否则你的代码是真的。确切地说,如果他的父亲做tmp-&gt;Father-&gt;Left = root;,则必须检查root是否是留子,否则tmp-&gt;Father-&gt;Right = root;

注意:正如您提到的,您的代码不处理删除具有两个子节点的节点。

【讨论】:

  • 感谢您的注意,刚刚进行了调整。但仍然崩溃!
  • @Asynchronousx 将this-&gt;value = NULL 添加到您的 TreeNode 构造函数中。一开始根的垃圾值不是NULL
  • @Asynchronousx 如果我一开始删除50,你的代码不会删除它!
  • 是的,因为 50 是根,我没有编写任何代码来擦除它!
  • 你。是。这。男人。我正试图从这些问题中找出答案,这让我非常头疼!!!非常感谢你帮我做这个 Bonje,你太棒了!你能解释一下为什么当我在右边/左边时我必须用根更新另一边吗?谢谢!!
【解决方案2】:

由于已经有一个答案为您提供了纠正特定错误的指示,因此我将尝试专注于一个可以帮助您一起避免类似错误的建议:

尝试将您当前的功能一分为二:

  1. 使用特定键搜索节点的函数,例如:Node* search(int key) 函数返回指向具有所需键的节点的指针或 nullptr,或使用您已有的。

  2. 删除(并重新连接)作为指针传递的节点并返回:下一个、上一个等:Node* delete(Node* n)

然后调用search,测试nulltpr,如果不同,将返回的指针作为输入参数传递给delete

通过这种方式,您可以轻松检测出问题在哪个阶段:搜索或删除。


P.S.:找出重新布线的错误通常是通过图表(方框和箭头)来完成的。决定你应该做什么,把它分成几个步骤并实施它。

【讨论】:

  • 我用 Visual Studio 调试我的程序,一步一步地检查指针值和每一步的值本身。似乎搜索代码行运行良好,因为它找到了我想要的节点,我遇到麻烦的是删除节点,当时我的节点只有一个儿子。
  • @Asynchronousx 确切地说,如果问题仅与 delete 部分有关,那么您和其他可能阅读您的人都更容易阅读和最终检测到代码。
  • 但问题仅与删除有关,因为其他一切都像魅力一样工作。唯一让我头疼的部分是删除单节点案例。 (左或右)
【解决方案3】:

好吧,一旦知道DEBUG版本使用sentinel值,在代码中发现问题就变得容易多了。

0xDD 用于死内存。那是已经被删除的记忆。因此,当调试器停止并告诉您指针错误且数据包含大量 0xDD 时,您就知道数据已被删除。此时,您应该检查包含数据的类以查看它们是否也被删除,以便您知道在将一个对象嵌入另一个对象时删除了哪些对象。

请注意,如果某些操作使用删除内存,有时您可能会在部分类中更改某些数据。查看内存模式也有助于发现未初始化的内存和其他类似问题。

其他一些链接:

在像你这样的情况下,如果你遵循编写单元测试的良好做法,那么找到问题就更容易了。事实上,如果您进行了适当的测试,那么您将测试所有可能的情况,这样您就会知道哪些情况失败了,这将帮助您找到可能做错的地方。

【讨论】:

  • 非常感谢您提供这些有用的信息。
【解决方案4】:

我想为@Bonje Fir 的答案添加一些内容。 当然这是一个正确的答案,但部分是:我会解释原因。

他建议交换我写的最后一段代码:

案例:我们在右子树中,我们想删除 70(因为我们没有叶子节点 60):

          50
       /     \
      30      70
     /  \       \
   20   40      80 

现在,使用@Bonje Fir 建议我们的代码,我们会在这里遇到问题:

TreeNode *tmp = root;
root = root->Right; //new root is the right son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Left (instead of Right)  = root; //linking the son to the new father

因为代码是说,一旦你用他的儿子更新了新的根,就将前一个根的父亲(我们保存在一个 tmp 变量中)链接到他的左儿子。然后我们会协助这样的事情:

          50
       /     x
      80      80
     /  \       
   20   40     

这是不一致的。

现在看看另一边,代码相同,没有叶子节点 20:

      50
   /      \
  30       70
    \     /  \
    40   60  80

代码适合这里,因为我们在正确的子树中。 因此,一旦将 30 更新为 40 (root = root -&gt; right),情况将是这样的:

      50
   x      \
  40       70
          /  \
         60  80

那么@Bonje Fir 写给我们的那段代码就非常适合:

tmp->Father->Left = root

因为可以肯定的是,我们将 40 分配给原始根的父亲的左儿子。 (因为我们在左子树中。)

      50
   /      \
  40       70
          /  \
         60  80

所以我做了一点改动来纠正这个逻辑问题,让它在左右子树中都有效。

else if (!root->Left) {
            TreeNode *tmp = root;
            root = tmp->Right;
            root->Father = tmp->Father; //linking the father to the new son

            //we need also to connect the son with the father, but first
            //we need to know in which subtree we're in.

            if (root->Father->Right == tmp) //if we're in the right subtree
                tmp->Father->Right = root;
            else                            ////if we're in the left subtree
                tmp->Father->Left = root;

            delete tmp;

            std::cout << "Erased!" << std::endl;                
        }

我利用了我没有删除我的根的事实,一旦分配了新的根,所以根的父亲仍然指向旧的根。

(相反情况下的相同讲话。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    • 1970-01-01
    相关资源
    最近更新 更多