【问题标题】:c++ delete specific node with two children from binary search treec ++从二叉搜索树中删除具有两个孩子的特定节点
【发布时间】:2018-02-16 03:03:12
【问题描述】:

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

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

这里是delete函数的代码,下面是一个节点有多个子节点的情况

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

我今天重写了很多次。我需要它来删除(显然)而不破坏树的完整性,它现在正在这样做。

【问题讨论】:

  • 为了澄清,findMin函数需要返回int类型
  • 您在寻找算法吗? Wikipedia 对 BST 中的 Deletion 有很好的解释(即使有插图)。

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


【解决方案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. 有一个root 没有暴露在 OP 样本中?缺少全局变量?忘记重命名?

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

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

所以,我彻底回顾了这个功能:

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 创建的,它必须是deleted。 (否则,任何删除的节点都会产生内存泄漏。)

  2. 返回的node *tOld 的left 和right 指针未重置。这可能是也可能不是问题(取决于如何对返回的指针进行后处理)。偏执的开发人员 [wc] 可以替换任何
    return tOld;
    作者
    return tOld-&gt;left = tOld-&gt;right = nullptr, tOld;

  3. 样本包含

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

    显然可以更短的写成

    if (t-&gt;left) {

    随着代码片段的移动而演变。我决定将其保留在当前状态,因为对于入门级的人来说,整个代码可能不容易理解。


OP 没有公开MCVE。因此,我取了一个旧样本并添加了以前不存在的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

BSTreePrint.h – 使用某种 ASCII 艺术打印二叉搜索树的模板函数:

#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 – 一个测试程序,用于构建样本树并删除各种节点以测试个别案例:

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

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

$ 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


$

注意:

左孩子打印在父母上方,右孩子在下方。我在准备测试代码时意识到了这一点。 (因此,在这种情况下,将头转向左侧以从上到下查看树木是行不通的,因为树木看起来像是“镜像”的。)请在检查结果时记住这一点。

【讨论】:

  • 我用MCVE扩展了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-18
相关资源
最近更新 更多