为了我的学术目的,我试图用C ++管理BST。
除了DeleteNode
功能之外,我没有任何问题
我选择使用class
而不是struct
来实现此数据结构。
问题是,我无法弄清楚如何使删除功能正常工作,我的调试器通常会出现0xDDDDDDDDD
错误,有时我可以删除节点,有时我的程序会崩溃。
我认为这可能是指针问题,但我无法弄清楚我做错了什么。
这是我的删除节点功能,我遇到了严重问题:
编辑:无子删除案例完全,我生气的是单子案例删除。
//function that delete a selected node
void DeleteNode(TreeNode* root,int key) {
/*we got three case here:*/
//until we find the right node with value in the tree
if (root->getValue() != key && root != nullptr) {
if (root->getValue() > key) {
DeleteNode(root->Left, key);
}
else if (root->getValue() < key) {
DeleteNode(root->Right, key);
}
}
else { //when we found the right node, then operate
/* THIS WORKS PERFECTLY! */
//first case: our node got no right and left son
if (!root->Left && !root->Right) {
TreeNode* tmp = root->Father;
if (tmp->Left == root) { //if the son is a left son
tmp->Left = nullptr;
delete root;
}
else if (tmp->Right == root) { //if the son is a right son
tmp->Right = nullptr;
delete root;
}
}
//second case: our node got a left but no right son
/* THIS ONE DOESN'T WORK. */
else if (!root->Right) {
TreeNode *tmp = root;
root = root->Left; //new root is the left son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Left = root; //linking the son to the new father
delete tmp;
std::cout << "Erased!" << std::endl;
}
else if (!root->Left) {
TreeNode *tmp = root;
root = root->Right; //new root is the right son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Right = root; //linking the son to the new father
delete tmp;
std::cout << "Erased!" << std::endl;
}
}
}
我尝试了很多组合,但结果每次都相同:它在第一次出现InOrder
显示功能时崩溃。 (当它没有时,该函数只删除第一个节点,然后当我尝试删除一个新节点时崩溃。)
这是一个简单的主要内容,我试图执行删除操作:
int main()
{
TreeNode root;
root.insertNode(&root,50);
root.insertNode(&root,30);
root.insertNode(&root,20);
root.insertNode(&root,40);
root.insertNode(&root,70);
root.insertNode(&root,60);
root.insertNode(&root,80);
for (int i = 0; i < 5; i++) {
int n;
cin >> n;
root.DeleteNode(&root, n);
cout << "In-Order: "; root.inOrder(&root);
cout << endl;
cout << "Pre-Order: "; root.preOrder(&root);
cout << endl;
cout << "Post-Order: "; root.postOrder(&root);
cout << endl;
}
}
这是我的完整BST代码(除了我之前提交的删除代码,只是为了更加完整地了解我的代码)
class TreeNode {
private:
int value;
TreeNode* Left;
TreeNode* Right;
TreeNode* Father;
public:
//constructor
TreeNode() {
this->Right = nullptr;
this->Left = nullptr;
this->Father = nullptr;
}
TreeNode(int value) {
this->value = value;
this->Right = nullptr;
this->Left = nullptr;
this->Father = nullptr;
}
//functions
int getValue() { return value; }
void setValue(int value) { this->value = value; }
//function to create a new node and insert a value into it
TreeNode* insertNode(TreeNode* root, int value) {
if (root->getValue() == NULL) {
root->setValue(value);
root->Father = nullptr;
}
else {
if (value > root->getValue()) {
if (root->Right) {
insertNode(root->Right, value);
}
else
root->Right = new TreeNode(value);
root->Right->Father = root;
}
else if (value < root->getValue()) {
if (root->Left) {
insertNode(root->Left, value);
}
else
root->Left = new TreeNode(value);
root->Left->Father = root;
}
}
return root;
}
//function to search a value into a BST
TreeNode* SearchNode(TreeNode* root, int key) {
if (root->getValue() == key) {
return root;
}
else if (root->getValue() < key) {
if (root->Right) {
SearchNode(root->Right, key);
}
else return nullptr;
}
else if (root->getValue() > key) {
if (root->Left) {
SearchNode(root->Left, key);
}
else return nullptr;
}
}
//function that return the height of the tree
int TreeHeigth(TreeNode* root) {
int heigth;
if (root == nullptr) {
return 0;
}
else {
return heigth = 1 + max(TreeHeigth(root->Left), TreeHeigth(root->Right));
}
}
//function that returns the number of the nodes
int CountTreeNode(TreeNode* root) {
if (root == nullptr) {
return 0;
}
else {
return CountTreeNode(root->Left) + CountTreeNode(root->Right) + 1;
}
}
//function that returns the minimum values into the tree
TreeNode* MinimumNode(TreeNode* root) {
if (root == nullptr) {
return nullptr;
}
while (root->Left != nullptr) {
root = root->Left;
}
return root;
}
//function that returns the maximum value into the tree
TreeNode* MaximumNode(TreeNode* root) {
if (root == nullptr) {
return nullptr;
}
while (root->Right != nullptr) {
root = root->Right;
}
return root;
}
//function that returns a successor of a given nodeb
TreeNode* SuccessorNode(TreeNode* node) {
//first case: our node got a rigth subtree:
if (node->Right != nullptr) {
return MinimumNode(node->Right);
}
//second case: our node doesnt got a right subtree: lets get
//upper in the tree until our node isn't a left child.
TreeNode* Ancestor = node->Father;
while (Ancestor != nullptr && node == Ancestor->Right) {
node = Ancestor;
Ancestor = Ancestor->Father;
}
}
//function tht returns a predecessor of a given node
TreeNode* PredecessorNode(TreeNode* node) {
//first case: (inverse to successor) our node got a left subtree:
if (node->Left != nullptr) {
return MaximumNode(node->Left);
}
TreeNode* Ancestor;
if (node->Father == nullptr)
return nullptr;
else
Ancestor = node->Father;
while (Ancestor != nullptr && node == Ancestor->Left) {
node = Ancestor;
Ancestor = Ancestor->Father;
}
return Ancestor;
}
//function that prints information about nodes
void InfoNode(TreeNode *root) {
root != nullptr ? std::cout << "Nodo corrente: " << root->getValue() << std::endl
: std::cout << "Nodo corrente: " << "NULL" << std::endl;
root->Father != nullptr? std::cout << "Padre: " << root->Father->getValue() << std::endl
: std::cout << "Padre: " << "NULL" << std::endl;
root->Left != nullptr ? std::cout << "Figlio SX: " << root->Left->getValue() << std::endl
: std::cout << "Figlio SX: " << "NULL" << std::endl;
root->Right!= nullptr ? std::cout << "Figlio DX: " << (root->Right)->getValue() << std::endl
: std::cout << "Figlio DX: " << "NULL" << std::endl;
}
//visits of a tree
void preOrder(TreeNode* root) {
if (root != nullptr) {
std::cout << root->getValue() << " ";
preOrder(root->Left);
preOrder(root->Right);
}
}
void inOrder(TreeNode* root) {
if (root != nullptr) {
inOrder(root->Left);
std::cout << root->getValue() << " ";
inOrder(root->Right);
}
}
void postOrder(TreeNode *root) {
if (root != nullptr) {
postOrder(root->Left);
postOrder(root->Right);
std::cout << root->getValue() << " ";
}
}
//max between 2 numbers
int max(int a, int b) {
return a > b ? a : b;
}
};
我正在努力研究的树的代表:
50
/ \
30 70
/ \ / \
20 40 60 80
我做错了什么?
答案 0 :(得分:3)
请注意以下条件:root->getValue() != key && root != nullptr
,首先调用getValue
,然后检查root
具有合法价值。交换它们(root != nullptr && root->getValue() != key
)。
最后,我认为您必须将最后一行更改为tmp->Father->Left = root;
以修复崩溃问题。
TreeNode *tmp = root;
root = root->Right; //new root is the right son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Right = root; //linking the son to the new father
PS:也为另一方做这个交换...
注意:这是真的,直到root
留给他父亲的孩子,否则你的代码是真的。如果父亲做root
其他tmp->Father->Left = root;
tmp->Father->Right = root;
是否留给孩子。
注意:正如您所说,您的代码无法处理具有两个childern的节点的删除。
答案 1 :(得分:2)
由于已经有答案给出了纠正特定错误的指示,我将尝试着重于一个可以帮助您避免类似错误的建议:
尝试将当前功能分成两部分:
使用特定密钥搜索节点的人,例如:Node* search(int key)
函数返回指向具有所需密钥的节点或nullptr
的指针,或者使用已有的密钥
删除(并重新连接)作为指针传递的节点并返回:next,previous等的:Node* delete(Node* n)
。
然后调用search
,针对nulltpr
进行测试,如果不同,则将返回的指针作为delete
中的输入参数传递。
通过这种方式,您可以轻松检测出您遇到的问题:搜索或删除。
P.S。:通常通过图表(方框和箭头)来确定重新布线错误。决定你应该做什么,将其分成几步并实施。
答案 2 :(得分:2)
好吧,一旦有人知道DEBUG版本使用了哨兵值,那么在代码中找到问题会变得更加微不足道。
0xDD用于死记忆。那是已经删除的内存。因此,当调试器停止并且它告诉您有一个错误的指针并且数据包含大量0xDD时,您知道数据已被删除。此时,您应该检查包含数据的类,以查看它们是否也被删除,以便您知道哪些对象在嵌入另一个对象时被删除。
请注意,如果某些操作使用删除内存,有时您可能会在某些类中更改某些数据。查看内存模式也有助于发现未初始化的内存和其他类似问题。
其他一些链接:
在像您这样的案例中,如果您遵循编写单元测试的良好实践,那么找到问题甚至会更加微不足道。事实上,如果你进行了适当的测试,那么你将测试所有可能的案例,这样你就会知道哪些案例失败了,这将有助于你找到可能出错的地方。
答案 3 :(得分:1)
我想在@Bonje Fir的回答中添加一些东西。 当然这是一个正确的答案,但部分是:我会解释原因。
他建议交换我写的最后一段代码:
案例:我们在正确的子树中,我们想要删除70(因为我们不再有叶子节点60):
50
/ \
30 70
/ \ \
20 40 80
现在,使用@Bonje Fir建议我们的代码,我们会遇到一个问题:
TreeNode *tmp = root;
root = root->Right; //new root is the right son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Left (instead of Right) = root; //linking the son to the new father
因为代码说的是,一旦你用他的儿子更新了新根,就将他之前的根(我们保存在tmp变量中)的父亲与他的左儿子联系起来。然后我们会帮助这样的事情:
50
/ x
80 80
/ \
20 40
那是不一致的。
现在看一下另一边,使用相同的代码并且没有叶节点20:
50
/ \
30 70
\ / \
40 60 80
代码适合这里,因为我们在正确的子树中。
所以一旦用40(root = root -> right
)更新30,情况就是这样:
50
x \
40 70
/ \
60 80
然后@Bonje Fir给我们编写的代码片段完全适合:
tmp->Father->Left = root
因为当然,我们将40分配给原始根的父亲的左儿子。 (因为我们在左边的子树中。)
50
/ \
40 70
/ \
60 80
所以我做了一些改动来纠正这个逻辑问题,并让它在左右子树中都能正常工作。
else if (!root->Left) {
TreeNode *tmp = root;
root = tmp->Right;
root->Father = tmp->Father; //linking the father to the new son
//we need also to connect the son with the father, but first
//we need to know in which subtree we're in.
if (root->Father->Right == tmp) //if we're in the right subtree
tmp->Father->Right = root;
else ////if we're in the left subtree
tmp->Father->Left = root;
delete tmp;
std::cout << "Erased!" << std::endl;
}
我利用了我没有擦除我的根的事实,一旦分配了新根,所以root的父亲仍然指向旧根。
(相反案例的同一发言。)