二叉查找树的查找、插入、删除、释放等基本操作的实现(C语言)

二叉查找树是一种特殊性质的二叉树,该树中的任何一个节点,它的左子树(若存在)的元素值小于节点的元素值,右子树(若存在)的元素值大于节点的元素值。

实现了二叉树查找树的实现以及基本操作,包括查找、插入、删除、初始化、释放等。

源码下载地址:http://download.csdn.net/detail/mcu_tian/9548788

二叉查找树节点的结构定义

相比二叉树,二叉查找树的元素结构定义稍微有点改动,即在元素结构体中添加了frequency成员,用来记录重复的元素。

当为新的元素的时候,frequency成员为1,每当有重复的成员函数插入到树种的时候,frequency加1。具体如下:

typedef char TreeElementType;
struct TreeNode
{
    TreeElementType element;
    unsigned int frequency;//当有重复的元素的时候的计数
    struct TreeNode *right;
    struct TreeNode *left;
};
typedef struct TreeNode *nodePtr;
typedef struct TreeNode *tree;
typedef struct TreeNode node;


二叉查找树MakeEmpty操作

该操作主要用于树的初始化,即将一棵树初始化为一颗空树,递归释放树中的节点元素。

其实现如下;

/*以递归的方式传递进行树节点的释放*/
tree MakeEmptyTree(tree root)
{
    if(root != NULL)
    {
        MakeEmptyTree(root->left);
        MakeEmptyTree(root->right);
        free(root);
        root = NULL;
    }
    return root;
}


二叉查找树的插入操作SearchTreeInsert

进行插入操作的基本思路为

1:递归查找插入元素的树中位置,申请节点,插入到树中

2:根据二叉查找树的性质,当插入的元素大于节点元素的时候,向右子树确定元素插入位置,当小于某节点元素的时候,向左子树确定元素插入位置

3:当树中有与插入元素相等的节点,则将该节点结构中的frequency成员加1

其具体实现如下:

void SearchTreeInsert(TreeElementType x,tree *root)
{
    if((*root)==NULL)
    {
        (*root) =  (nodePtr) malloc(sizeof(node));

        if((*root) == NULL)
        {
            printf("can't malloc ,insert operation failed");
        }
        (*root)->frequency = 1;
        (*root)->element = x;
        (*root)->left = NULL;
        (*root)->right = NULL;
        return;
    }
    if(x > ((*root)->element))
    {
        SearchTreeInsert(x,&(*root)->right);
        return;
    }
    if(x < ((*root)->element))
    {
        SearchTreeInsert(x,&(*root)->left);
        return;
    }
    if(x == ((*root)->element))
    {
        ++((*root)->frequency);
    }
}


二叉查找树的查找操作Find、FindMin、Findmax

二叉查找树的基本查找操作有三种

nodePtr Find(TreeElementType x,tree root)函数的功能为查找root树中是否有x元素,若有则返回元素节点结构的指针,若没有则返回NULL

nodePtr FindMax(tree root)函数的功能返回root查找树中的最大元的指针,若root为空,则返回NULL。

nodePtr FindMin(tree root)函数的功能返回root查找树中的最小元的指针,若root为空,则返回NULL。

以上三种函数的实现可以通过递归和迭代两种方法。

Find

在本次的实现中Find操作的实现使用的是递归实现,代码如下:

nodePtr Find(TreeElementType x,tree root)
{
    if(root != NULL)
    {
        if(root->element < x)
        {
            return Find(x,root->right);
        }
        if(root->element > x)
        {
            return Find(x,root->left);
        }
    }
    return root;
}

FindMax和FindMin

FindMax可以用递归,也可以用迭代

下面通过迭代的方式实现进行最大值的查找

递归的查找的思路如下:

nodeptr FindMax(tree root)

{  if(root == NULL)

    { return NULL;}

    if(root->right != NULL)

    { return FindMax(root);}

    return root;

}

FindMin递归实现同上

其迭代实现的源码如下

FindMax

nodePtr FindMax(tree root)
{
    if(root == NULL)
    {
        return NULL;
    }
    while((root->right)!= NULL)
    {
        root = root->right;
    }
    return root;
}

FindMin

nodePtr FindMin(tree root)
{
    if(root == NULL)
    {
        return NULL;
    }
    while((root->left) != NULL)
    {
        root = root->left;
    }
    return root;
}

二叉查找树的删除操作delete

二叉查找树的删除操作是二叉查找树中最难的部分

删除节点target有如下四种情况

1;删除节点target的频率大于1

2:删除节点target为叶子节点

3:删除节点target只有左孩子或者右孩子

4:删除节点target只有既有右孩子也有左孩子


删除节点target的频率大于1在这种情况下,只需要将target结构体中的frequency成员的值减1

if(targetPtr->frequency > 1)
    {
        --targetPtr->frequency;
        return;
    }

删除节点target为叶子节点

找到target节点的父节点,并删除target节点

 if((targetPtr->left == NULL)&&(targetPtr->right == NULL))
    {
        if((*root) == targetPtr)
        {
            free(targetPtr);
            (*root) = NULL;
            return;
        }
        tmpPtr = *root;   //找到父亲节点
        while(tmpPtr != NULL)
        {
            if(x < tmpPtr->element)
            {
                if(tmpPtr->left == targetPtr) break;
                    else tmpPtr = tmpPtr->left;
            }
            if(x > tmpPtr->element)
            {
                if(tmpPtr->right == targetPtr) break;
                    else tmpPtr = tmpPtr->right;
            }
        }
        free(targetPtr);
        tmpPtr->left = NULL;
        tmpPtr->right = NULL;
        return;
    }

删除节点target只有左孩子或者右孩子

只有一个孩子节点的时候,只需要将孩子替换掉被删除的节点即可

该方法中,具体实现为将目标节点target的孩子节点赋值到target中,然后删除孩子节点

即 target = leftchild or target = rightchild   then delete righchild or leftchild

其实现代码如下

 if((targetPtr->left != NULL)&&(targetPtr->right == NULL))
    {
        tmpPtr = targetPtr->left;
        
        targetPtr->element = tmpPtr->element;
        targetPtr->left = tmpPtr->left;
        targetPtr->right = tmpPtr->right;
        targetPtr->frequency = tmpPtr->frequency;

        free(tmpPtr);
        return;
    }

    if((targetPtr->left == NULL)&&(targetPtr->right != NULL))
    {
        tmpPtr = targetPtr->right;

        targetPtr->element = tmpPtr->element;
        targetPtr->left = tmpPtr->left;
        targetPtr->right = tmpPtr->right;
        targetPtr->frequency = tmpPtr->frequency;

        free(tmpPtr);
        return;
    }

删除节点target只有既有右孩子也有左孩子

当删除节点有左右孩子的时候,用右子树的最小值代替删除的节点(也可以用左子树的最大值代替),本次实现中,使用的是右子树的最小值。

在找到最小值替换之后,就需要将最初的最小值的节点删除,由于右子树中的最小值只可能有右孩子,不可能有左孩子

当删除最小节点的时候有如下两种情况

1:最小值节点有右孩子节点,跟删除节点target只有左孩子或者右孩子的思路类似,只需要将右孩子替换掉该节点。

2:最小值节点为叶子节点,找到该叶子节点的父节点,删除该节点,并将父节点得右孩子指针设为NULL。

具体代码实现如下:

if((targetPtr->left != NULL)&&(targetPtr->right != NULL))
    {
        minPtr = FindMin(targetPtr->right); //最小值肯定没有左子树

        targetPtr->element = minPtr->element;

        if((minPtr->right == NULL))
        {
            tmpPtr = targetPtr;
            if(tmpPtr->right == minPtr) //最小值为左子树的第一个节点
            {
                free(minPtr);
                tmpPtr->left = NULL;
                tmpPtr->right = NULL;
                return;
            }
            tmpPtr = tmpPtr->right;
            while(tmpPtr != NULL)
            {
                if(tmpPtr->left == minPtr)
                {
                    free(minPtr);
                    tmpPtr->left = NULL;
                    return;
                }
                tmpPtr = tmpPtr->left;
            }
        }

删除还有其他的实现方法,如懒惰删除。
删除操作不多的情况下,可以使用
懒惰删除:当一个元素要被删除的时候,仍旧留在树中,只是做了个被删除的记号,同时频率的数减1

测试:

在源码的main函数中

运行程序截图如下

《二叉查找树的查找、插入、删除、释放等基本操作的实现(C语言)》


除了delete和makeEmpty之外,对于所有的操作都是花费O(logN)的时间,因为我们用的常数时间在书中降低一层,对树的操作大概减少一半左右

二叉树的平均深度是O(log N),但是当反复进行删除和插入操作时候,有可能会导致树不平衡,比如左右子树之间的深度有着很大的差。

这种情况就会导致之前树的基本操作消耗,和深度的优势就会大大的折扣,这种情况显然不是乐于见到的。

对于上面,可以在进行删除操作的时候,我们随机选择用右子树的最小元素和左子树的最大元素来替代被删除的元素以缓解这种问题,但是不排除极端的问题。

还有一种方法,即使用平衡二叉树,即AVL树。


参考资料:数据结构与算法分析:C语言描述(原书第2版)




    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/mcu_tian/article/details/51668883
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞