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

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

本文关键字:两个 子节点 节点 搜索树 删除 C++      更新时间:2023-10-16

我目前正在开发一个程序来处理 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;
}

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

起初,我阅读了我在评论中推荐的维基百科文章二叉搜索树中的删除。

提问者提到

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

因此,提问者似乎认为理解算法,但不知道如何正确实现最后一部分。因此,我尝试帮助:

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)

因此,我完整地回顾了该功能:

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 *tOldleftright指针不会重置。这可能是也可能不是问题(取决于返回的指针的后处理方式)。偏执的开发人员[wc]ould通过return tOld->left = tOld->right = nullptr, tOld;替换
    任何
    return 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

$

注意:

左边的孩子印在父母上面,右边的孩子印在下面。我在准备测试代码时意识到了这一点。(因此,将头部向左转动以获得自上而下的树木视图不起作用,因为在这种情况下,树木看起来是"镜像的"。请在检查结果时记住这一点。