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;
}
我今天把这篇文章改写了很多次。我需要它删除(显然)而不会破坏树的完整性,它现在正在这样做。
起初,我阅读了我在评论中推荐的维基百科文章二叉搜索树中的删除。
提问者提到
我知道如何删除有两个孩子的节点的逻辑,但代码目前对我不起作用。
因此,提问者似乎认为理解算法,但不知道如何正确实现最后一部分。因此,我尝试帮助:
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;
仔细观察整个功能,我意识到其余部分似乎是有问题的:
有没有在OP样本中暴露的
root
?缺少全局变量?忘记重命名?我对如何删除当前节点的事实感到有些困惑。一方面,提供指向当前节点的指针作为参考(我个人也会这样做)。另一方面,对于当前节点的替换,当前节点在父节点中标识(使用无效的
getParent()
辅助函数)。这不是必需的,因为节点指针可以直接更改(并且会影响原始指针)。这就是为什么它是一个参考(node* &t
)。风格问题:
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;
}
removeKey()
返回已删除的节点(如果未找到键,则返回nullptr
)。这很重要,因为可能需要一些后处理来释放节点。如果删除的节点是使用new
创建的,则必须delete
d.(否则,任何删除的节点都会产生内存泄漏。返回
node *tOld
的left
和right
指针不会重置。这可能是也可能不是问题(取决于返回的指针的后处理方式)。偏执的开发人员[wc]ould通过return tOld->left = tOld->right = nullptr, tOld;
替换
任何return tOld;
该示例包含
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 ∁
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
$
注意:
左边的孩子印在父母上面,右边的孩子印在下面。我在准备测试代码时意识到了这一点。(因此,将头部向左转动以获得自上而下的树木视图不起作用,因为在这种情况下,树木看起来是"镜像的"。请在检查结果时记住这一点。
- 如何使用发送数据包所花费的时间计算两个节点之间的距离?
- 检查两个节点在子节点上是否具有相同状态的更优雅的方法
- 比较两个节点坐标的最佳方法是什么?
- 使用 Dijkstra 算法跟踪两个节点之间的最短路径
- 比较C++中两个BST的节点
- 给定两个字符串 S 和 T.确定与 T 相差最小的 S 子字符串?
- 图问题:找出两个节点是否在每个节点的O(1)时间和O(2)存储中共享同一分支
- 如何在单个链表中交换两个节点的位置,只修改指针
- 如何遍历具有两个节点的链接节点
- 是否可以使一个类成为两个不同层次结构的子类?
- 查找C++中两个索引之间的子字符串
- 子数组中两个数字的相同出现(连续)
- C++ 链表合并排序的实现在连接 1 个以上节点的子列表时失败
- 查找树中两个节点之间的最大成本边
- 添加一个节点,并在通用树中的两个给定节点之间找到路径成本,其中c 中的儿童列表
- 使用位于 C 字符串中的两个字符*获取子字符串
- 难以通过具有两个类和一个 LLL 的头节点
- 查找树(不属于任何特定类型的简单连接树)中两个节点之间的路径
- 链接列表C 类,这两个添加节点实现之间的差异是什么?
- 有没有办法在两个ROS节点之间具有优先级