c ++从二叉搜索树中删除具有两个子节点的特定节点

时间:2018-02-16 03:03:13

标签: c++ tree binary-tree binary-search-tree

我目前正在开发一个用c ++处理BST的程序。我的所有函数当前都在工作,但removeNode除外,它删除了树中给定键值的节点。我认为前两个案例有效,但第三个案件给我带来了麻烦。我知道如何删除有两个孩子的节点的逻辑,但目前代码对我不起作用。这是节点

struct node{
    int key;
    node* left;
    node* right;
};

以下是删除功能的代码,底部有一个以上子节点的节点

node* Tree::removeKey(node*& t, int k)
{
    if(t == nullptr){
        return root;
    }
    else if(k < t->key){
        root->left = removeKey(t->left, k);
    }
    else if (k > t->key){
        root->right = removeKey(t->right, k);
    }


    else {
        node* parent = getParent(root, k);
        // Case 1:  No child
        if(t->left == nullptr && t->right == nullptr) {
            if(parent->left->key == t->key){
                parent->left = nullptr;
                return t;
            }
            if(parent->right->key == t->key){
                parent->right = nullptr;
                return t;
            }

        }
        //Case 2: One child
        else if(t->left != nullptr && t->right == nullptr) {
            parent->left = t->left;
            parent->right = nullptr;
            return t;

        }
        else if(t->right != nullptr && t->left == nullptr) {
            parent->right = t->right;
            parent->left = nullptr;
            return t;
        }
        // case 3: 2 children
        else {



//            node *temp = nullptr;
 //           temp->key = findMin(root->right);
//            t->key = temp->key;
//            t->right = removeKey(t->right,temp->key);
//            return t;
        }
    }

还有,辅助函数getParent,

node* Tree::getParent(node* t, int k)
{
    if(t == nullptr){
        return nullptr;
    }else
        if((t->left != nullptr && k == t->left->key ) ||
           (t->right != nullptr && k == t->right->key)){
            return t;
        }else
            if(k < t->key){
                return getParent(t->left, k);
            }
            if(k > t->key){
                    return getParent(t->right, k);
            }
    return nullptr;
}

今天我已经多次重写这篇文章了。我需要它删除(显然)而不破坏它正在做的树的完整性。

1 个答案:

答案 0 :(得分:1)

首先,我自己阅读了我在评论中推荐的二元搜索树中的维基百科文章Deletion

提问者提到

  

我知道如何删除有两个孩子的节点的逻辑,但目前代码对我不起作用。

所以看来,提问者相信理解算法,但不知道如何正确实现最后一部分。因此,我试着帮助:

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;

仔细观察整个功能,我意识到剩下的似乎是错误的:

  1. 在OP的样本中有一个root没有公开?缺少全局变量?忘了重命名?

  2. 我对如何删除当前节点感到困惑。一方面,提供当前节点的指针作为参考(我个人也会这样做)。另一方面,为了替换当前节点,在父节点中标识当前节点(使用无效的getParent()辅助函数)。这不是必需的,因为可以直接更改节点指针(并且将影响原始指针)。这就是 node* &t)的原因。

  3. 风格问题:
    if (t == nullptr)可以写成if (t)
    if (t != nullptr)可以写成if (!t)

  4. 所以,我完全回顾了这个功能:

    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. removeKey()返回已删除的节点(如果未找到密钥,则返回nullptr)。这很重要,因为可能需要一些后处理来释放节点。如果删除的节点是使用new创建的,则必须为delete d。 (否则,将为任何已删除的节点生成内存泄漏。)

    2. 未重置返回的left的{​​{1}}和right指针。这可能是也可能不是问题(取决于返回的指针是如何进行后处理的)。偏执的开发者[wc]可以取代任何一个 node *tOld
      通过
      return tOld;

    3. 样本包含

      return tOld->left = tOld->right = nullptr, tOld;

      显然可以缩写为

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

      这是在改变代码片段的过程中演变而来的。我决定将其置于当前状态,因为对于有入门级别的人来说,整个代码可能不容易理解。

    4. OP没有公开MCVE。因此,我采用了我的一个旧样本并添加了if (t->left) {之前不存在的内容:

      BSTreeT::remove() - 二叉搜索树的模板类:

      BSTreeT.h

      #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 - 用某种ASCII艺术打印二叉搜索树的模板函数:

      BSTreePrint.h

      #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

      在Windows 10上的cygwin中进行了编译和测试:

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

      注意:

      左边的孩子印在父母身上,右下面的孩子。我在准备测试代码时意识到了这一点。 (因此,将树头向左转以获得树木的自上而下的视图在树木出现时不起作用#34;镜像&#34;在这种情况下。)请在检查结果时记住这一点。 / p>