数据结构 --- 二叉搜索树的操作(递归和非递归版本)

二叉搜索树又称二叉排序树,它要么是一颗空树,要么满足以下性质:
1)如果左子树不为空,则左子树上所有节点的值都小于根节点的值;
2) 如果右子树不为空,则右子树上所有节点的值都大于根节点的值;
3)它的左右子树也分别为二叉搜索树。

如图所示:
这里写图片描述
接下来,我们对二叉搜索树进行插入、查找和删除操作:

正式操作前的一些准备工作:

typedef char SearchNodeType;

typedef struct SearchNode
{
    SearchNodeType data;
    struct SearchNode* lchild;
    struct SearchNode* rchild;
}SearchNode;

void SearchTreeInit(SearchNode** pRoot)
{
    if(pRoot == NULL)
    {
        return;//非法输入
    }
    *pRoot = NULL;
    return;
}

SearchNode* CreateSearchNode(SearchNodeType value)
{
    SearchNode* new_node = (SearchNode*)malloc(sizeof(SearchNodeType));
    new_node->data = value;
    new_node->lchild = NULL;
    new_node->rchild = NULL;
     return new_node;
}

void DestroySearchNode(SearchNode* root)
{
    free(root);
}
void SearchTreeDestroy(SearchNode** pRoot)
{
    if(pRoot == NULL)
    {
        return;//非法输入
    }
    if(*pRoot == NULL)
    {
        return;//空树
    }
    SearchNode* root = *pRoot;
    SearchTreeDestroy(&root->lchild);
    SearchTreeDestroy(&root->rchild);
    DestroySearchNode(root);
    *pRoot = NULL;
    return;
}

二叉搜索树的插入

在二叉搜索树中插入新元素时,必须先检测该元素在树中是否已经存在。如果存在,则不进行插入,直接返回;否则将新元素插入到搜索停止的地方。
插入的具体过程:
1.树为空,直接插入,然后返回
2.树不为空,按二叉搜索树的性质查找插入位置,插入新节点

递归版本:

void SearchTreeInsert(SearchNode** pRoot,SearchNodeType to_insert)
{
    if(pRoot == NULL)
    {
        return;//非法输入
    }
    if(*pRoot == NULL)
    {
        //空树,直接插入
        SearchNode* new_node = CreateSearchNode(to_insert);
        *pRoot = new_node;
        return;
    }
    //对于不是空树的情况,非递归的插入
    SearchNode* cur = *pRoot;
    if(to_insert < cur->data)
    {
        //递归的往左子树插入
        SearchTreeInsert(&cur->lchild,to_insert);
    }
    else if(to_insert > cur->data)
    {
        //递归的往右子树插
        SearchTreeInsert(&cur->rchild,to_insert);
    }
    else
    {
        //对于相当的情况,直接返回
        //此时不允许树中有重复的元素
        return;
    }
}

非递归版本:

//非递归的实现二叉搜索树的插入、查找和删除操作
void SearchTreeInsert(SearchNode** pRoot,SearchNodeType to_insert)
{
    if(pRoot == NULL)
    {
        return;//非法输入
    }
    if(*pRoot == NULL)
    {
        //空树,直接插入
        *pRoot = CreateSearchNode(to_insert);
        return;
    }
    //表示新元素要插入的位置
    SearchNode* cur = *pRoot;
    //代表新元素的父节点
    SearchNode* pre = NULL;
   //借助下面这个循环帮我们找到要插入的位置
    while(1)
    {
        if(cur == NULL)
        {
            //找到要插入的地方了,跳出循环
            break;
        }
        if(to_insert < cur->data)
        {
            pre = cur;
            cur = cur->lchild;
        }
        else if(to_insert > cur->data)
        {
            pre = cur;
            cur = cur->rchild;
        }
        else
        {
            //不允许有重复的元素,直接返回
            return;
        }
    }
    //这里pre不可能为空,因为如果pre为空,
    //说明刚一进循环就满足cur == NULL,
    //而cur为空的情况在前面已经讨论过了
    SearchNode* new_node = CreateSearchNode(to_insert);
    if(new_node->data < pre->data)
    {
        pre->lchild = new_node;

    }
    else
    {
        pre->rchild = new_node;
    }
    return;
}

二叉搜索树的查找

查找也是遵循二叉搜索树的性质的原则:要查找的元素的值小于当前节点,就在它的左子树中查找;如果大于当前节点的值,就在它的右子树中查找;如果相等,直接返回当前节点所在的位置。

递归版本:

//递归的查找
SearchNode* SearchTreeFind(SearchNode* root,SearchNodeType to_find)
{
    if(root == NULL)
    {
        return NULL;//空树
    }
    if(to_find < root->data)
    {
        //递归的在左子树中查找
       return  SearchTreeFind(root->lchild,to_find);
    }
    else if(to_find > root->data)
    {
        //递归的在右子树中查找
       return  SearchTreeFind(root->rchild,to_find);
    }
    else
    {
        //相等,直接返回
        return root;
    }
}

非递归版本:

SearchNode* SearchTreeFind(SearchNode* root,SearchNodeType to_find)
{
    if(root == NULL)
    {
        return NULL;//空树
    }
    //cur用于树的遍历
    SearchNode* cur = root;
    while(1)
    {
        //遍历结束
        if(cur == NULL)
        {
            break;
        }
        //在左子树中寻找
        if(to_find < cur->data)
        {
            cur = cur->lchild;
        }
        else if(to_find > cur->data)
        {
            //在右子树中找
            cur = cur->rchild;
        }
        else
        {
            //相等,直接返回当前位置
            return cur;
        }
    }
    //遍历结束,还没有找到,说明不存在,返回空
    return NULL;
}

二叉搜索树的删除

思路:首先查找要删除的元素是否在二叉搜索树中,如果不存在,直接返回;如果存在,分以下四种情况讨论:
1)要删除的节点无左右子树
2)要删除的节点只有左子树
3)要删除的节点只有右子树
4)要删除的节点有左、右子树
对上述四种情况,相应的删除操作为:
1)直接删除该节点
2)删除该节点且使被删除节点的父节点指向被删除节点的左子树
3)删除该节点且使被删除节点的父节点指向被删除节点的右子树
4)在要删除的节点的右子树中寻找最小值,用它的值填补到被删除节点,来处理该节点的删除问题。
递归版本:

void SearchTreeRemove(SearchNode** pRoot,SearchNodeType to_remove)
{
    if(pRoot == NULL)
    {
        return;//非法输入
    }
    if(*pRoot == NULL)
    {
        return;//空树
    }
    //1.先找到要删除节点所在的位置
    SearchNode* root = *pRoot;
    //2.如果没找到,直接返回;
    if(to_remove < root->data)
    {
        SearchTreeRemove(&root->lchild,to_remove);
        return;
    }
    else if(to_remove > root->data)
    {
        SearchTreeRemove(&root->rchild,to_remove);
        return;
    }
    else
    {
        //3.如果找到了,分情况讨论
        SearchNode* to_remove_node = root;
        //  a)该节点没有子树;
        if(root->lchild == NULL && root->rchild == NULL) 
        {
            DestroySearchNode(to_remove_node);
            *pRoot = NULL;
            return;
        }
        //  b)该节点只有左子树;
        else if(root->lchild != NULL && root->rchild == NULL)
        {
            *pRoot = to_remove_node->lchild;
            DestroySearchNode(to_remove_node);
            return;
        }
        //  c)该节点只有右子树;
        else if(root->lchild == NULL && root->rchild != NULL)
        {
            *pRoot = to_remove_node->rchild;
            DestroySearchNode(to_remove_node);
            return;
        }
        //  d)该节点有左右子树。
        else
        {
            //先找到右子树中的最小值;
            //然后将要删除的节点和最小节点交换;
            //最后从右子树出发,尝试递归的删除刚刚交换的值。
            SearchNode* min = to_remove_node->rchild;
            if(min->lchild != NULL)
            {
                min = min->lchild;
            }
            //min此时已将指向了要删除节点右子树中的最小节点
            //这里我们可以不用交换,直接将最小节点的值赋给要删除节点
            to_remove_node->data = min->data;
            //然后尝试递归的删除min->data
            SearchTreeRemove(&to_remove_node,min->data);
            //return;
        }

    }
    return;
}

非递归版本:

oid SearchTreeRemove(SearchNode** pRoot,SearchNodeType to_remove)
{
    if(pRoot == NULL)
    {
        return;//非法输入
    }
    if(*pRoot == NULL)
    {
        return;//空树
    }
    //1.先找到要删除节点所在的位置
    SearchNode* to_remove_node = *pRoot;
    SearchNode* parent = NULL;
    while(1)
    {
        if(to_remove_node == NULL)
        {
            //2.如果没找到要删除的元素,直接返回
            return;
        }
        if(to_remove < to_remove_node->data)
        {
            parent = to_remove_node;
            to_remove_node = to_remove_node->lchild;
        }
        else if(to_remove > to_remove_node->data)
        {
            parent = to_remove_node;
            to_remove_node = to_remove_node->rchild;
        }
        else
        {
            //找到要删除元素所在的位置,跳出循环
            break;
        }
    }
        //3.找到要删除元素所在的位置,分情况讨论
        // a)要删除的元素没有左右子树
        if(to_remove_node->lchild == NULL && to_remove_node->rchild == NULL)
        {
            //此处我们要先判断要删除的节点是否是根结点
            if(to_remove_node == *pRoot)
            {
                //要删除的节点是根结点
                *pRoot = NULL;
            }
            else
            {
                //要删除的节点不是根结点
                //这里我们需要知道要删除的节点是其父节点parent的左子树还是右子树
                if(to_remove_node->data < parent->data)
                {
                    parent->lchild = NULL;
                }
                else
                {
                    parent->rchild = NULL;
                }
            }
            //统一释放节点
            DestroySearchNode(to_remove_node);
            return;
        }
        // b)只有左子树
        else if(to_remove_node->lchild != NULL && to_remove_node->rchild == NULL)
        {
            if(to_remove_node == *pRoot)
            {
                //要删除节点是根结点
                *pRoot = to_remove_node->lchild;
            }
            else
            {
                if(to_remove_node->data < parent->data)
                {
                    parent->lchild = to_remove_node->lchild;
                }
                else
                {
                    parent->rchild = to_remove_node->lchild;
                }
            }
            //统一释放节点
            DestroySearchNode(to_remove_node);
            return;
        }
        // c)只有右子树
       else  if(to_remove_node->lchild == NULL && to_remove_node->rchild != NULL)
        {
            if(to_remove_node == *pRoot)
            {
                *pRoot = to_remove_node->rchild;
            }
            else
            {
                if(to_remove_node->data < parent->data)
                {
                    parent->lchild = to_remove_node->rchild;
                }
                else
                {
                    parent->rchild = to_remove_node->rchild;
                }
            }
            //统一释放节点
            DestroySearchNode(to_remove_node);
        }
        // d)有左右子树
        else
        {
            //这块和递归的一样
            //先找到要删除节点右子树中的最小值
            //然后将其值赋给要删除元素。最后删除这个最小值
            //min用于遍历要删除节点的右子树,从其右子树出发,来找到最小值
            SearchNode* min = to_remove_node->rchild;
            //min_parent用于标记最小值的父节点 
            SearchNode* min_parent = to_remove_node;
            while(min->lchild == NULL)
            {
                min_parent = min;
                min = min->lchild;
            }
            //循环结束,此时min指向了右子树中的最小值
            to_remove_node->data = min->data;
            if(min->data < min_parent->data)
            {
                //min是min_parent的左子树
                //min一定没有左子树
                min_parent->lchild = min->rchild;
            }
            else
            {
                //通常情况下,min是min_parent的左子树
                //但是初始情况例外
                min_parent->rchild = min->rchild;
            }
        }
}

删除操作相对来说比较复杂,也很容易蒙圈。大家可以画一个图,对照着图来看。

猜你喜欢

转载自blog.csdn.net/y6_xiamo/article/details/80413137