如何在C中删除二进制搜索树中的节点级别?

时间:2018-05-02 21:44:55

标签: c recursion binary-search-tree

假设我们有以下BST:

      5           (level 0)
   3       8      (level 1)
2    4    7    9  (level 2)

当尝试删除级别0或1时,一切正常,但是当删除级别2.我得到分段错误

[1]    18636 segmentation fault  ./a.out

这是我的代码:

#include<stdio.h>
#include<stdlib.h>

struct node
{
    int key;
    struct node *left, *right;
};

// A function to create a new BST node
struct node *newNode(int item)
{
    struct node *temp =  (struct node *)malloc(sizeof(struct node));
    temp->key = item;
    temp->left = temp->right = NULL;
    return temp;
}

// A function to insert a new node with given key in BST
struct node* insert(struct node* node, int key)
{
    // If the tree is empty, return a new node
    if (node == NULL) return newNode(key);

    // Otherwise, recur down the tree
    if (key < node->key)
        node->left  = insert(node->left, key);
    else if (key > node->key)
        node->right = insert(node->right, key);

    // return the (unchanged) node pointer
    return node;
}

// Given a non-empty binary search tree, return the node with minimum
// key value found in that tree. Note that the entire tree does not
// need to be searched.
struct node * minValueNode(struct node* node)
{
    struct node* current = node;

    // loop down to find the leftmost leaf
    while (current->left != NULL)
        current = current->left;

    return current;
}

struct node* deleteNode(struct node* root, int key)
{
    // base case
    if (root == NULL) return root;

    // If the key to be deleted is smaller than the root's key,
    // then it lies in left subtree
    if (key < root->key)
        root->left = deleteNode(root->left, key);

    // If the key to be deleted is greater than the root's key,
    // then it lies in right subtree
    else if (key > root->key)
        root->right = deleteNode(root->right, key);

    // if key is same as root's key, then This is the node
    // to be deleted
    else
    {
        // node with only one child or no child
        if (root->left == NULL)
        {
            struct node *temp = root->right;
            free(root);
            return temp;
        }
        else if (root->right == NULL)
        {
            struct node *temp = root->left;
            free(root);
            return temp;
        }

        // node with two children: Get the inorder successor (smallest
        // in the right subtree)
        struct node* temp = minValueNode(root->right);

        // Copy the inorder successor's content to this node
        root->key = temp->key;

        // Delete the inorder successor
        root->right = deleteNode(root->right, temp->key);
    }
    return root;
}

void deleteGivenLevel(struct node* root, int level)
{
    if (root == NULL)
    {
        return;
    }
    if (level == 0)
    {
        printf("\n %d will be removed!", root->key);
        root = deleteNode(root, root->key);
    }
    else if (level > 0)
    {
        deleteGivenLevel(root->left, level-1);
        deleteGivenLevel(root->right, level-1);
    }
}

// A function to do inorder traversal of BST
void inorder(struct node *root)
{
    if (root != NULL)
    {
        inorder(root->left);
        printf("\t %d", root->key);
        inorder(root->right);
    }
}

int main()
{
    int input, level;
    struct node *root = NULL;

    printf("\nEnter a number to go into the BST: ");
    scanf("%d",&input);
    root = insert(root, input);

    while(1)
    {
        printf("\nEnter another number (0 to stop): ");
        scanf("%d",&input);
        if (input==0)
        {
            break;
        }
        insert(root, input);
    }

    // print inoder traversal of the BST
    printf("\nBefore Deletion:");
    inorder(root);

    printf("\nWhich level would you like to delete? ");
    scanf("%d",&level);
    deleteGivenLevel(root, level);

    // print inoder traversal of the BST
    printf("\nAfter Deletion:");
    inorder(root);
    return 0;
}

我还注意到它只在我尝试删除级别0或1时才有效。除此之外它不起作用。如果我添加更多节点,我尝试删除。在这种情况下,它根本不会删除任何内容。

1 个答案:

答案 0 :(得分:0)

问题是deleteNode

当您使用leaf(deleteNodeleft个孩子的节点呼叫right时 是NULL)因为它的参数,这个块被执行:

if (root->left == NULL)
{
    struct node *temp = root->right;
    free(root);
    return temp;
}
else if (root->right == NULL)
{
    struct node *temp = root->left;
    free(root);
    return temp;
}

这里的问题是你释放了叶子,但是父亲是叶子 指向叶子没有得到更新,因此它一直指向 释放内存,当你再次遍历树时,你访问无效 记忆。这会产生未定义的行为,并以段错误结束。

如果您逐步完成deleteGivenLevel功能,最终 root将是key == 3的节点。然后你做

deleteGivenLevel(root->left, level-1);

其中root->leftkey == 2level-1为0的叶子。这意味着 在下次通话中

if (level == 0)
{
    printf("\n %d will be removed!", root->key);
    root = deleteNode(root, root->key);
}
执行

rootkey == 2的叶子。这是问题所在, 该节点已被删除,但因为它是一个离开,所以key == 3的节点为 left元素指向叶子,不会更新。

快速解决方法是将父节点传递给deleteNodedeleteGivenLevel以便您可以正确更新父节点。但是你 应该重新考虑你删除策略。

struct node* deleteNode(struct node* root, int key, struct node *parent)
{
    // base case
    if (root == NULL) return root;

    // If the key to be deleted is smaller than the root's key,
    // then it lies in left subtree
    if (key < root->key)
        root->left = deleteNode(root->left, key, NULL); // you could also call
                                                        // with root as the
                                                        // 3rd argument, but
                                                        // you update the parent in this line
                                                        // anyway


    // If the key to be deleted is greater than the root's key,
    // then it lies in right subtree
    else if (key > root->key)
        root->right = deleteNode(root->right, key, NULL); // same as above

    // if key is same as root's key, then This is the node
    // to be deleted
    else
    {
        // node with only one child or no child
        if (root->left == NULL)
        {
            struct node *temp = root->right;

            // update only if parent is present
            if(parent)
            {
                if(root->key < parent->key)
                    parent->left = temp;
                else
                    parent->right = temp;
            }

            free(root);
            return temp;
        }
        else if (root->right == NULL)
        {

            struct node *temp = root->left;

            // update only if parent is present
            if(parent)
            {
                if(root->key < parent->key)
                    parent->left = temp;
                else
                    parent->right = temp;
            }

            free(root);
            return temp;
        }

        // node with two children: Get the inorder successor (smallest
        // in the right subtree)
        struct node* temp = minValueNode(root->right);

        // Copy the inorder successor's content to this node
        root->key = temp->key;

        // Delete the inorder successor
        root->right = deleteNode(root->right, temp->key, root);
    }
    return root;
}

void deleteGivenLevel(struct node* root, int level, struct node *parent)
{
    if (root == NULL)
    {
        return;
    }
    if (level == 0)
    {
        printf("\n %d will be removed!", root->key);
        root = deleteNode(root, root->key, parent);
    }
    else if (level > 0)
    {
        deleteGivenLevel(root->left, level-1, root);
        deleteGivenLevel(root->right, level-1, root);
    }
}

void free_tree(struct node *root)
{
    if(root == NULL)
        return;

    free_tree(root->left);
    free_tree(root->right);
    free(root);
}

现在,当您使用真实根调用deleteGivenLevel时,应设置parentNULL

我使用此main函数检查了代码,请注意deleteGivenLevel是如何进行的 使用parent == NULL调用:

int main()
{
    int level;
    struct node *root = NULL;

    int vals[] = { 5, 3, 8, 2, 4, 7, 9 };

    for(size_t i = 0; i < sizeof vals / sizeof *vals; ++i)
        root = insert(root, vals[i]);

    // print inoder traversal of the BST
    printf("\nBefore Deletion:");
    inorder(root);
    puts("");

    level = 2;
    deleteGivenLevel(root, level, NULL);

    // print inoder traversal of the BST
    printf("\nAfter Deletion:");
    inorder(root);
    puts("");

    free_tree(root);
    return 0;
}

这是输出:

$ valgrind ./a 
==24038== Memcheck, a memory error detector
==24038== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==24038== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==24038== Command: ./a
==24038== 

Before Deletion:     2   3   4   5   7   8   9

 2 will be removed!
 4 will be removed!
 7 will be removed!
 9 will be removed!
After Deletion:  3   5   8
==24038== 
==24038== HEAP SUMMARY:
==24038==     in use at exit: 0 bytes in 0 blocks
==24038==   total heap usage: 8 allocs, 8 frees, 1,192 bytes allocated
==24038== 
==24038== All heap blocks were freed -- no leaks are possible
==24038== 
==24038== For counts of detected and suppressed errors, rerun with: -v
==24038== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

我还检查了更多节点的代码并删除了不同的级别,我没有 得到一个单一的段错误,无论如何它似乎打印出正确的结果。