我最近在空闲时间实现了二叉树,我相信我能让它正常工作。但是,在运行简单测试时,我遇到了一个神秘的分段错误。这是我对二叉搜索树的实现:
//the binary search tree class
template <class M>
class BS_Tree {
private:
//node structure
struct Node {
M data; //the key of the node
Node* left,* right,* parent; //node pointers to left right and parent
Node(M key, Node* p) //parameterized node constructor
: data(key), left(nullptr), right(nullptr), parent(p) {}
};
Node* root; //the root node of the binary search tree
bool is_left(Node* n) {return n->parent->left == n;} //utility function
void destroy(Node* n); //used for tree destruction
void duplicate(Node* const &, Node* &); //used for copy construction
public:
BS_Tree() {root = nullptr;} //constructor
~BS_Tree() {destroy(root);} //destructor
BS_Tree(const BS_Tree &); //copy constructor
BS_Tree &operator=(const BS_Tree &); //copy assignment operator
Node* find(M key); //find function
void insert(M); //insert function
void erase(Node*); //erase function
Node* get_root() {return root;}
};
//destroy function used for tree destruction
template <class M>
void BS_Tree<M>::destroy(Node* n) {
if(n) { //recursively erase the tree
if(n->left) destroy(n->left);
if(n->right) destroy(n->right);
delete n;
}
}
//duplicate function used for copy construction
template <class M>
void BS_Tree<M>::duplicate(Node* const &one, Node* &two) {
if(!one) two = nullptr;
else { //recursively duplicate the tree
two = new Node(one->data);
duplicate(one->left, two->left);
duplicate(one->right, two->right);
}
}
//copy constructor
template <class M>
BS_Tree<M>::BS_Tree(const BS_Tree &b) {
if(!b.root) root = nullptr; //update root
else duplicate(b.root, this->root); //call duplicate function
}
//copy assignment operator
template <class M>
BS_Tree<M> &BS_Tree<M>::operator=(const BS_Tree &b) {
if(!b.root) root = nullptr; //update root
else { //destroy current tree and duplicate source tree
this->~BS_Tree();
duplicate(b.root, this->root);
}
}
//function to find a key and return a pointer
template <class M>
typename BS_Tree<M>::Node* BS_Tree<M>::find(M key) {
Node* i = root; //create an index
while(i) {
//try to find the key
if (i->data == key) return i;
if(i->data > key) i = i->left;
else i = i->right;
}
//return a pointer to the key, nullptr if not found
return i;
}
//function to insert a new node
template <class M>
void BS_Tree<M>::insert(M key) {
if(!root) { //if no tree, make new node the root
root = new Node(key, nullptr);
return;
}
Node* i = root; //create an index
while(true) { //find insertion point and insert new node
if(i->data > key) {
if(!i->left) {
i->left = new Node(key, i);
return;
}
else i = i->left;
}
if(i->data <= key) {
if(!i->right) {
i->right = new Node(key, i);
return;
}
else i = i->right;
}
}
}
//Function to erase a node
template <class M>
void BS_Tree<M>::erase(Node* n) {
if(n) {
//no children case
if(!n->left && !n->right) {
if(root == n) root = nullptr; //if node is root, make root null
else { //if node is a child, update parent's children
if(is_left(n)) n->parent->left = nullptr;
else n->parent->right = nullptr;
}
delete n; //erase the node
return;
}
//one child cases
if(!n->left) {
if(n == root){ //if node is root, update root
root = n->right;
n->right->parent = nullptr;
} else { //if node is a child, update parent's children and nodes parent
if(is_left(n)) n->parent->left = n->right;
else n->parent->right = n->right;
n->right->parent = n->parent;
}
delete n; //erase the node
return;
}
if(!n->right) {
if(n == root){ //if node is root, update root
root = n->left;
n->left->parent = nullptr;
} else { //if node is a child, update parent's children and nodes parent
if(is_left(n)) n->parent->left = n->left;
else n->parent->right = n->left;
n->left->parent = n->parent;
}
delete n; //erase the node
return;
}
//two children case
else {
Node* i = n; //create an index
i = i->right; //find successor
while(i->left) i = i->left;
n->data = i->data; //set nodes data to successor's data
if(is_left(i)) i->parent->left = i->right; //update successor's parent and its child
else i->parent->right = i->right;
if(i->right) i->right->parent = i->parent;
delete i; //erase successor node
return;
}
}
}
对于测试,它是一个简单的线性向量元素插入与时间性能检查。每次测试需要最多的元素和元素的增量。然后它为每个增量运行测试并对插入进行计时。它在前几次迭代(即n = 10000,20000,30000,40000)中工作正常,但是在调用50000元素树析构函数后插入后会出现分段错误。它暗示着这条线:
if(n->right) destroy(n->right);
我知道我正在进行增量插入,所以我的所有元素都在树的右侧,但是我很难弄清楚我哪里出错了,为什么它只会弄乱这个迭代。
我的测试实施:
int main(){
//number of elements, max test elements, and test increment
int n, t = 100000, i = 10000;
//run the test a number of times
for(n = i; n <= t; n += i) {
//get an incremental vector of size n
std::vector<int> test(n);
std::iota(std::begin(test), std::end(test), 0);
//run the insert test print the time interval
BS_Tree<int> m;
auto ibs_start = clock();
for(auto i : test){
m.insert(i);
}
auto ibs_stop = clock();
std::cout << n << ' ' << (ibs_stop - ibs_start)/double(CLOCKS_PER_SEC)*1000 << "\n";
}
return 0;
}
如果有人可以提供帮助,我会非常感激!
编辑:由于destroy函数必须在到达底部之前存储所有元素并实际删除任何内容,它可能只是我的堆栈溢出吗?
答案 0 :(得分:0)
如果它适用于某些项目(40000),但不适用于较大的项目,则可能是堆栈溢出。查看代码也证实了这一点:破坏是递归的,并且在非常不平衡的树的情况下,它意味着与许多节点一样多的堆栈帧。
问题是如何在没有递归的情况下解决这个问题?这不是那么明显,因为有两个递归调用。我的解决方案是遍历二叉树,后序和删除叶节点。
这是一个解决方案 - destroy方法的迭代版本:
//destroy function used for tree destruction
template <class M>
void BS_Tree<M>::destroy(Node* n) {
if (n) { // iteratively erase the tree
Node* c = n; // current node
while (true) {
if (c->right) {
// it has right child, descend to it
c = c->right;
continue;
}
if (c->left) {
// it has left child, descend to it
c = c->left;
continue;
}
// this node has no (more) children, destroy it
Node* p = c->parent;
if (p) {
// update its parent to no longer point to it
if (p->right == c) p->right = NULL; // we came from left
else if (p->left == c) p->left = NULL; // we came from left
}
// destroy the node
delete c;
// go back to its parent
c = p;
if (c == n) {
// where at back at start node, stop
delete n;
break;
}
}
}
}
更新:我测试了我的解决方案,并进行了一些小改动。