c++ delete specific node with two children from binary search tree

后端 未结 1 913
失恋的感觉
失恋的感觉 2021-01-27 23:03

I\'m currently working on a program to handle a BST in c++. I have all of my functions currently working, except removeNode, which deletes a node of given key value in the tree.

相关标签:
1条回答
  • 2021-01-27 23:43

    At first, I read myself the Wikipedia article Deletion in Binary Search Trees which I recommended in the my comment.

    The questioner mentions

    I know the logic of how to delete a node with two children, but the code is not working for me at the moment.

    So it seems, the questioner believes to understand the algorithm but does not know how to implement the last part correctly. Thus, I try to help:

    node *tOld = t; // remember current top node (which shall be removed)
    // find left-most child in right sub-tree
    t = t->right;
    if (!t->left) {
      // special case: old right child becomes new top node
    } else {
      // traverse right sub-tree left down
      node *parent = t; // old parent of new top node
      for (; t->left; parent = t, t = t->left);
      // swap nodes
      parent->left = t->right; // old parent of left most child gets right child (or nullptr)
      t->right = tOld->right; // new top node gets old right sub-tree
    }
    t->left = tOld->left; // new top node gets old left sub-tree
    // return remove node
    return tOld;
    

    Looking closer at the whole function, I realized that the rest seems to be buggy:

    1. There is referred a root which is not exposed in sample of OP? Missing global variable? Forgot to rename?

    2. I was confused a bit by the fact how the removal of current node is done. On one hand, the pointer to current node is provided as reference (which I personally would do as well). On the other hand, for the replacement of current node, the current node is identified in parent (using the ineffective getParent() helper function). This is not necessary as the node pointer can be changed directly (and will affect the original pointer). That's why it is a reference (node* &t).

    3. A question of style:
      if (t == nullptr) can be written as if (t)
      if (t != nullptr) can be written as if (!t).

    So, I reviewed the function completely:

    node* Tree::removeKey(node *&t, int k)
    {
      // This happens if key is not found.
      if (!t) return t;
      // This is traversal by recursion to find the node to remove.
      if (k < t->key) return removeKey(t->left, k);
      if (k > t->key) return removeKey(t->right, k);
      // This is the case where node with k has been found:
      node *tOld = t; // remember current top node for return
      // Case 1: No child
      if (!t->left && !t->right) {
        /* Override t
         * if t is root -> tree becomes empty
         * if t is left of parent node -> parent->left becomes empty
         * if t is right of parent node -> parent->right becomes empty
         */
        t = nullptr;
        return tOld;
      }
      // Case 2: One child
      if (t->left && !t->right) {
        t = t->left;
        return tOld;
      }
      if (t->right && !t->left) {
        t = t->right;
        return tOld;
      }
      // Case 3: 2 children
      // find left-most child in right sub-tree
      t = t->right;
      if (!t->left) {
        // special case: old right child becomes new top node
      } else {
        // traverse right sub-tree left down
        node *parent = t; // old parent of new top node
        for (; t->left; parent = t, t = t->left);
        // swap nodes
        parent->left = t->right; // old parent of left most child gets right child (or nullptr)
        t->right = tOld->right; // new top node gets old right sub-tree
      }
      t->left = tOld->left; // new top node gets old left sub-tree
      return tOld;
    }
    
    1. The removeKey() returns the removed node (or nullptr if key was not found). That's important as there is probably some post-processing necessary to release the node. If the removed node was created with new it must be deleted. (Otherwise, memory-leaks are produced for any removed node.)

    2. The left and right pointer of the returned node *tOld are not reset. This may or may not be an issue (depending how returned pointer is post-processed). Paranoid developers [wc]ould replace any
      return tOld;
      by
      return tOld->left = tOld->right = nullptr, tOld;

    3. The sample contains

      if (!t->left) {
        // special case: old right child becomes new top node
      } else {

      which obviously could be shorter written as

      if (t->left) {

      This evolved while shifting code pieces around. I decided to leave this in current state as the whole code is probably not easy to understand for somebody with entry level.


    The OP didn't expose an MCVE. Hence, I took an old sample of mine and added BSTreeT::remove() which didn't exist before:

    BSTreeT.h – a template class for a binary search tree:

    #ifndef B_S_TREE_T_H
    #define B_S_TREE_T_H
    
    /* provides a less functor which simply wraps operator < for a certain
     * type
     *
     * VALUE ... C++ type of value to less-compare
     */
    template <typename VALUE>
    struct lessFunc {
      bool operator()(const VALUE &value1, const VALUE &value2) const
      {
        return value1 < value2;
      }
    };
    
    /* provides a class template for a binary search tree.
     *
     * KEY ... C++ type of the key values of nodes
     * VALUE ... C++ type of the other values of nodes
     * COMP ... C++ type of 
     */
    template <typename KEY, typename VALUE, typename COMP = lessFunc<KEY> >
    class BSTreeT {
    
      public:
        // node type
        class Node {
          /* This friend shall ensure that the corresponding
           * BSTreeT template class may access private _pLeft and _pRight.
           */
          friend class BSTreeT<KEY, VALUE, COMP>;
    
          public:
            // the key value of node (used to define an order)
            const KEY key;
            // other values of nodes
            VALUE value;
          private:
            // pointers to left and right child nodes
            Node *_pLeft, *_pRight;
    
          private: // Construction/destruction is for exclusive use of BSTreeT.
            // constructor.
            Node(const KEY &key, const VALUE &value):
              key(key), value(value), _pLeft(nullptr), _pRight(nullptr)
            { }
            // destructor.
            ~Node() { delete _pLeft; delete _pRight; }
            // disabled:
            Node(const Node&) = delete;
            Node& operator=(const Node&) = delete;
    
          public:
            // returns pointer to left child node (or nullptr if there is none).
            const Node* getLeft() const { return _pLeft; }
            // returns pointer to right child node (or nullptr if there is none).
            const Node* getRight() const { return _pRight; }
        };
    
      public:
        // less functor used to compare node keys
        const COMP &comp;
      private:
        // root pointer
        Node *_pRoot;
    
      public:
        /* constructor.
         *
         * comp ... a less comparator to define order of nodes
         */
        explicit BSTreeT(const COMP &comp = COMP()):
          comp(comp), _pRoot(nullptr)
        { }
        // destructor.
        ~BSTreeT() { delete _pRoot; }
        // disabled:
        BSTreeT(const BSTreeT&) = delete;
        BSTreeT& operator=(const BSTreeT&) = delete;
    
      public:
        /* inserts a node.
         *
         * key ... the key value of node
         * value ... the other value of node
         * return: true ... key/value inserted
         *         false ... Error! Possible reasons:
         *           - duplicated key
         *           - allocation of node failed.
         */
        bool insert(const KEY &key, const VALUE &value)
        {
          return insert(_pRoot, key, value);
        }
        /** removes a node.
         *
         * key ... the key value of node to remove
         * return: true ... key/value inserted
         *         false ... Error! Possible reasons:
         *           - key not found
         */
        bool remove(const KEY &key)
        {
          return remove(_pRoot, key);
        }
        /* provides a functor-like type which is applied to every node
         * in traverse().
         *
         * If an instance of this class is provided the traverse() does nothing
         * else than the pure traversal.
         */
        struct Apply {
          // pre-order access to node
          virtual void preNode(Node &node) { }
          // in-order access to node
          virtual void inNode(Node &node) { }
          // post-order access to node
          virtual void postNode(Node &node) { }
        };
        /* traverses the tree and applies the provided object to every node.
         *
         * apply ... the action object applied to every node
         */
        void traverse(Apply &apply)
        {
          if (_pRoot) traverse(_pRoot, apply);
        }
        /* provides a functor-like type which is applied to every const node
         * in traverse().
         *
         * If an instance of this class is provided the traverse() does nothing
         * else than the pure traversal.
         */
        struct ConstApply {
          // pre-order access to node
          virtual void preNode(const Node &node) { }
          // in-order access to node
          virtual void inNode(const Node &node) { }
          // post-order access to node
          virtual void postNode(const Node &node) { }
        };
        /* traverses the tree and applies the provided object to every node.
         *
         * apply ... the action object applied to every node
         */
        void traverse(ConstApply &apply) const
        {
          if (_pRoot) traverse(_pRoot, apply);
        }
    
      private:
        // inserts a node.
        bool insert(Node *&pTree, const KEY &key, const VALUE &value)
        { /* Every if-branch ends with return.
           * Thus, no explict else is needed.
           */
          if (!pTree) { /* (!pTree) ... (pTree == nullptr) */
            return !!(pTree = new Node(key, value));
          }
          if (comp(key, pTree->key)) return insert(pTree->_pLeft, key, value);
          if (comp(pTree->key, key)) return insert(pTree->_pRight, key, value);
          return false;
        }
        // removes a node.
        bool remove(Node *&pNode, const KEY &key)
        {
          // This happens if key is not found.
          if (!pNode) return false;
          // This is traversal by recursion to find the node to remove.
          if (key < pNode->key) return remove(pNode->_pLeft, key);
          if (key > pNode->key) return remove(pNode->_pRight, key);
          // This is the case where node with key has been found:
          Node *pNodeOld = pNode; // remember current node for delete
          // Case 1: No child
          if (!pNode->_pLeft && !pNode->_pRight) pNode = nullptr;
          /* Override pNode
           * if pNode is _pRoot -> tree becomes empty
           * if pNode is _pLeft of parent node -> parent->_pLeft becomes empty
           * if pNode is _pRight of parent node -> parent->_pRight becomes empty
           */
          // Case 2: One child
          else if (pNode->_pLeft && !pNode->_pRight) pNode = pNode->_pLeft;
          else if (pNode->_pRight && !pNode->_pLeft) pNode = pNode->_pRight;
          // Case 3: 2 children
          else {
            // find left-most child in right sub-tree
            pNode = pNode->_pRight;
            if (pNode->_pLeft) {
              // traverse right sub-tree left down
              Node *pParent = pNode; // old parent of new top node
              for (; pNode->_pLeft; pParent = pNode, pNode = pNode->_pLeft);
              // swap nodes
              pParent->_pLeft = pNode->_pRight; // old parent of left most child gets right child (or nullptr)
              pNode->_pRight = pNodeOld->_pRight; // new top node gets old right sub-tree
            } // else: special case: old right child becomes new top node
            // new top node gets old left sub-tree
            pNode->_pLeft = pNodeOld->_pLeft;
          }
          // delete old node
          pNodeOld->_pLeft = pNodeOld->_pRight = nullptr;
          delete pNodeOld;
          // done with success
          return true;
        }
        // tries to find a node by key.
        Node* find(Node *pTree, const KEY &key) const
        {
          if (comp(key, pTree->key)) {
            return pTree->_pLeft ? find(pTree->_pLeft, key) : nullptr;
          }
          if (comp(pTree->key, key)) {
            return pTree->_pRight ? find(pTree->_pRight, key) : nullptr;
          }
          return pTree;
        }
        // traverses the tree.
        void traverse(Node *pTree, Apply &apply)
        {
          apply.preNode(*pTree);
          if (pTree->_pLeft) traverse(pTree->_pLeft, apply);
          apply.inNode(*pTree);
          if (pTree->_pRight) traverse(pTree->_pRight, apply);
          apply.postNode(*pTree);
        }
        // traverses the tree.
        void traverse(const Node *pTree, ConstApply &apply) const
        {
          apply.preNode(*pTree);
          if (pTree->_pLeft) traverse(pTree->_pLeft, apply);
          apply.inNode(*pTree);
          if (pTree->_pRight) traverse(pTree->_pRight, apply);
          apply.postNode(*pTree);
        }
    };
    
    #endif // B_S_TREE_T_H
    

    BSTreePrint.h – template functions to print a binary search tree with some kind of ASCII art:

    #ifndef B_S_TREE_PRINT_H
    #define B_S_TREE_PRINT_H
    
    #include <cassert>
    #include <iostream>
    #include <string>
    #include "BSTreeT.h"
    
    namespace {
    
    /* a derived tree-traversal action
     * for graphical (i.e. ASCII-art) pre-order output of tree
     */
    template <typename K, typename V, typename C>
    struct PrintPreT: public BSTreeT<K, V, C>::ConstApply {
    
      typedef BSTreeT<K, V, C> Tree;
    
      std::ostream &out;
      std::string indent;
    
      explicit PrintPreT(std::ostream &out): out(out), indent("  ") { }
      ~PrintPreT() = default;
      PrintPreT(const PrintPreT&) = delete;
      PrintPreT operator=(const PrintPreT&) = delete;
    
      virtual void preNode(typename Tree::Node const &node)
      {
        indent.pop_back(); char c = indent.back(); indent.pop_back();
        out << indent << "+-"
          << (node.getLeft() || node.getRight() ? '+' : '-')
          << '-' << node << '\n';
        indent += c; indent += ' ';
        indent += node.getRight() ? "| " : "  ";
      }
      virtual void inNode(typename Tree::Node const &node)
      {
        indent.pop_back(); indent.pop_back();
        indent += "  ";
      }
      virtual void postNode(typename Tree::Node const &node)
      {
        indent.pop_back(); indent.pop_back();
      }
    };
    
    /* a derived tree-traversal action
     * for graphical (i.e. ASCII-art) in-order output of tree
     */
    template <typename K, typename V, typename C>
    struct PrintInT: public BSTreeT<K, V, C>::ConstApply {
    
      typedef BSTreeT<K, V, C> Tree;
    
      std::ostream &out;
      std::string indent;
    
      explicit PrintInT(std::ostream &out): out(out), indent("  ") { }
      ~PrintInT() = default;
      PrintInT(const PrintInT&) = delete;
      PrintInT operator=(const PrintInT&) = delete;
    
      virtual void preNode(typename Tree::Node const&)
      { 
        indent += "  ";
      }
      virtual void inNode(typename Tree::Node const &node)
      {
        popIndent();
        const char l = popIndent() == ' ' ? '|' : ' ';
        const bool root = indent.empty();
        out << indent
          << (root ? "--" : "+-")
          << (node.getLeft() || node.getRight() ? "+-" : "--")
          << node << '\n';
        indent += root ? ' ' : l; indent += ' ';
        indent += "| ";
      }
      virtual void postNode(typename Tree::Node const&)
      {
        popIndent();
      }
    
      char popIndent()
      {
        indent.pop_back(); const char c = indent.back(); indent.pop_back();
        return c;
      }
    };
    
    } // namespace
    
    template <typename K, typename V, typename C>
    std::ostream& printPre(
      std::ostream &out, const BSTreeT<K, V, C> &tree)
    {
      PrintPreT<K, V, C> printer(out);
      tree.traverse(printer);
      return out;
    }
    
    template <typename K, typename V, typename C>
    std::ostream& printIn(
      std::ostream &out, const BSTreeT<K, V, C> &tree)
    {
      PrintInT<K, V, C> printer(out);
      tree.traverse(printer);
      return out;
    }
    
    enum BSTreePrintStyle {
      PrintBSTreePreOrder,
      PrintBSTreeInOrder
    };
    
    template <typename K, typename V, typename C>
    std::ostream& print(
      std::ostream &out, const BSTreeT<K, V, C> &tree,
      BSTreePrintStyle style = PrintBSTreePreOrder)
    {
      switch (style) {
        case PrintBSTreePreOrder: return printPre(out, tree);
        case PrintBSTreeInOrder: return printIn(out, tree);
        default: assert(false);
      }
      return out;
    }
    
    #endif // B_S_TREE_PRINT_H
    

    testRemove.cc – a test program to build a sample tree and remove various nodes to test the individual cases:

    #include <iostream>
    #include "BSTreeT.h"
    #include "BSTreePrint.h"
    
    using namespace std;
    
    // template instances (for convenience)
    struct Empty { };
    typedef BSTreeT<char, Empty>::Node BSTreeNode;
    typedef BSTreeT<char, Empty> BSTree;
    
    ostream& operator<<(ostream &out, const BSTreeNode &node)
    {
      return out << node.key;
    }
    
    ostream& operator<<(ostream &out, const BSTree &tree)
    {
      return printIn(out, tree);
    }
    
    // recursive method to build balanced tree
    void buildTree(BSTree &tree, char begin, char end)
    {
      char middle = (begin + end) / 2;
      tree.insert(middle, Empty());
      if (begin < middle) buildTree(tree, begin, middle);
      if (middle < end) buildTree(tree, middle + 1, end);
    }
    
    // helper function
    void remove(BSTree &tree, char key)
    {
      cout << "Remove node '" << key << "': "
        << (tree.remove(key) ? "done" : "failed") << '\n'
        << tree << endl;
    }
    
    int main()
    {
      BSTree tree;
      buildTree(tree, 'A', 'Z');
      cout << "Initial tree:\n" << tree << endl;
      // Test Cases
      // test key not found
      remove(tree, '?');
      // test case 1
      remove(tree, 'K');
      // test case 2
      remove(tree, 'I');
      remove(tree, 'H'); // intermediate step (case 1)
      remove(tree, 'J');
      // test cases 3
      remove(tree, 'G');
      remove(tree, 'T');
      // done
      return 0;
    }
    

    Compiled and tested in cygwin on Windows 10:

    $ g++ --version 
    g++ (GCC) 6.4.0
    
    $ g++ -std=c++11 -o testRemove testRemove.cc
    
    $ ./testRemove
    Initial tree:
            +---A
          +-+-B
          | +---C
        +-+-D
        | | +---E
        | +-+-F
      +-+-G
      | |   +---H
      | | +-+-I
      | +-+-J
      |   | +---K
      |   +-+-L
    --+-M
      |     +---N
      |   +-+-O
      |   | +---P
      | +-+-Q
      | | | +---R
      | | +-+-S
      +-+-T
        |   +---U
        | +-+-V
        +-+-W
          | +---X
          +-+-Y
            +---Z
    
    Remove node '?': failed
            +---A
          +-+-B
          | +---C
        +-+-D
        | | +---E
        | +-+-F
      +-+-G
      | |   +---H
      | | +-+-I
      | +-+-J
      |   | +---K
      |   +-+-L
    --+-M
      |     +---N
      |   +-+-O
      |   | +---P
      | +-+-Q
      | | | +---R
      | | +-+-S
      +-+-T
        |   +---U
        | +-+-V
        +-+-W
          | +---X
          +-+-Y
            +---Z
    
    Remove node 'K': done
            +---A
          +-+-B
          | +---C
        +-+-D
        | | +---E
        | +-+-F
      +-+-G
      | |   +---H
      | | +-+-I
      | +-+-J
      |   +---L
    --+-M
      |     +---N
      |   +-+-O
      |   | +---P
      | +-+-Q
      | | | +---R
      | | +-+-S
      +-+-T
        |   +---U
        | +-+-V
        +-+-W
          | +---X
          +-+-Y
            +---Z
    
    Remove node 'I': done
            +---A
          +-+-B
          | +---C
        +-+-D
        | | +---E
        | +-+-F
      +-+-G
      | | +---H
      | +-+-J
      |   +---L
    --+-M
      |     +---N
      |   +-+-O
      |   | +---P
      | +-+-Q
      | | | +---R
      | | +-+-S
      +-+-T
        |   +---U
        | +-+-V
        +-+-W
          | +---X
          +-+-Y
            +---Z
    
    Remove node 'H': done
            +---A
          +-+-B
          | +---C
        +-+-D
        | | +---E
        | +-+-F
      +-+-G
      | +-+-J
      |   +---L
    --+-M
      |     +---N
      |   +-+-O
      |   | +---P
      | +-+-Q
      | | | +---R
      | | +-+-S
      +-+-T
        |   +---U
        | +-+-V
        +-+-W
          | +---X
          +-+-Y
            +---Z
    
    Remove node 'J': done
            +---A
          +-+-B
          | +---C
        +-+-D
        | | +---E
        | +-+-F
      +-+-G
      | +---L
    --+-M
      |     +---N
      |   +-+-O
      |   | +---P
      | +-+-Q
      | | | +---R
      | | +-+-S
      +-+-T
        |   +---U
        | +-+-V
        +-+-W
          | +---X
          +-+-Y
            +---Z
    
    Remove node 'G': done
            +---A
          +-+-B
          | +---C
        +-+-D
        | | +---E
        | +-+-F
      +-+-L
    --+-M
      |     +---N
      |   +-+-O
      |   | +---P
      | +-+-Q
      | | | +---R
      | | +-+-S
      +-+-T
        |   +---U
        | +-+-V
        +-+-W
          | +---X
          +-+-Y
            +---Z
    
    Remove node 'T': done
            +---A
          +-+-B
          | +---C
        +-+-D
        | | +---E
        | +-+-F
      +-+-L
    --+-M
      |     +---N
      |   +-+-O
      |   | +---P
      | +-+-Q
      | | | +---R
      | | +-+-S
      +-+-U
        | +---V
        +-+-W
          | +---X
          +-+-Y
            +---Z
    
    
    $
    

    Note:

    Left children are printed above parents, right children below. I realized this while preparing the test code. (Hence, turning the head to the left to get a top-down view of trees does not work as the trees appear "mirrored" in this case.) Please, keep this in mind while checking the results.

    0 讨论(0)
提交回复
热议问题