二叉树析构函数问题

时间:2016-10-13 21:02:14

标签: c++ debugging data-structures segmentation-fault binary-search-tree

我最近在空闲时间实现了二叉树,我相信我能让它正常工作。但是,在运行简单测试时,我遇到了一个神秘的分段错误。这是我对二叉搜索树的实现:

//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函数必须在到达底部之前存储所有元素并实际删除任何内容,它可能只是我的堆栈溢出吗?

1 个答案:

答案 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;
            }
        }
    }
}

更新:我测试了我的解决方案,并进行了一些小改动。