簡體   English   中英

C ++從二進制搜索樹中刪除具有兩個子節點的特定節點

[英]c++ delete specific node with two children from 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;
}

今天我已經重寫了很多次。 我需要它(顯然)刪除而不破壞樹的完整性,而樹的完整性現在正在這樣做。

首先,我讀了自己的Wikipedia文章“在二叉搜索樹中刪除” ,我在評論中建議這樣做。

發問者提到

我知道如何刪除有兩個孩子的節點的邏輯,但是該代碼目前對我不起作用。

如此看來,發問者相信能夠理解算法,但不知道如何正確實現最后一部分。 因此,我嘗試提供幫助:

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* &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創建的,則必須將其delete d。 (否則,將為任何刪除的節點生成內存泄漏。)

  2. leftright返回的指針node *tOld不能復位。 這可能是(也可能不是)問題(取決於返回的指針如何進行后處理)。 偏執的開發人員[wc]將替換任何
    return tOld;
    通過
    return tOld->left = tOld->right = nullptr, tOld;

  3. 該樣本包含

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

    顯然可以寫成

     if (t->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


$

注意:

左邊的孩子印在父母的上方,右邊的孩子印在父母的上方。 我在准備測試代碼時意識到了這一點。 (因此,將頭向左旋轉以獲得樹的俯視圖是不起作用的,因為在這種情況下樹看起來是“鏡像的”。)請在檢查結果時記住這一點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM