zoukankan      html  css  js  c++  java
  • 数据结构总结系列(二)——二叉排序树

    什么是二叉排序树?二叉排序树和二叉树有什么关系呢?结合图像,让我们一点点来学习~

    首先要明确的是二叉排序树是查找中运用很多的动态查找表,关于动态查找和静态查找,我们这次只是点明一点点,具体的部分下次再讲~

    所谓动态查找,就是表在查找过程中动态生成我们所需要的表啦:

    那么二叉排序树又是动态查找表中比较厉害(雾)的一种。。。

    那么,二叉排序树有什么特点呢?

    因此我们可以了解到,二叉排序树仍然具有树的特点,同时它的内部子树仍然保持高度有序呢(单个节点拎出来仍然是一颗二叉排序树)!

    既然是树,最有效的存储方式当然是链表了~

    按照链表定义,轻松写出结构体:

    typedef struct BiNode
    {
        int data;
        BiNode *lchild=NULL;
        BiNode *rchild=NULL;
    }BiNode,*BiTree;//声明一棵树,以后可以使用

    这里的结构体对左右孩子的声明还有一种写法:

    struct BiNode *lchild=NULL;
    struct BiNode *rchild=NULL;

    既然是一棵树,接下来自然需要插入来生成了,但是我们需要注意,二叉排序树由于高度有序,插入的方式自然不同,我们认为最好是需要是对叶节点进行操作是最理想的状态,为了达到我们的目的,我们需要写一个遍历函数:

    bool SearchTree(BiTree T,int kval,BiTree f,BiTree &tmp)//树的查找,这里思考了很久tmp的作用,最后理解为之后有时会需要这节点来干一些神奇的事情
    {
        if(!T)//如果是空树,那么自然查不到了
        {
            tmp=f;
            return false;
        }
        else
        {
            if(T->data==kval)//如果此节点对应数据恰好吻合,bingo
            {
                tmp=T;
                return true;
            }
            else if(kval< (T->data)) SearchTree(T->lchild,kval,T,tmp);//这里的遍历查找自然不必多说
            else SearchTree(T->rchild,kval,T,tmp);
        }
    }

    我们能够对叶节点进行操作的话,自然可以用递归的方式进行新节点的插入操作,由于二叉排序树需要左边小于当前节点而右边大于当前节点,我们需要仔细考虑判断条件:

    话不多说,上代码:

    void CreatTree(BiTree &T,int data)//树的建立,非常简单,不必过多注释
    {
        BiTree tmp;
        if(!SearchTree(T,data,NULL,tmp))//这里是一定需要遍历到最下面的叶节点的,不然插入节点是一件非常麻烦的事情
        {
            BiNode *t=new BiNode;
            t->data=data;
            if(!tmp) T=t;
            else if(data< (tmp->data)) tmp->lchild=t;//若当前数据小于节点,自然插入左边啦
            else tmp->rchild=t;
            cout << "Insert successful" << endl;
        }
        else
            cout << "Insert failed" << endl;
    }

    我们成功的插入了新的叶节点,是否意味着我们的工作完成了2/3呢,不幸的是,节点的删除工作十分繁琐,需要考虑的情况非常多,对于叶节点的删除是一件简单的事情,而对于中间节点:删除之后依然保留二叉排序树的高度有序是一件非常麻烦的事情,好在前人已经帮我们整理好了我们需要考虑的情况:

     

    那么,上代码:

    void Delete(BiTree &p)
    {
        BiTree q;
        BiTree s;
        //从树中删除节点p
        //重新连接左右子树
        if(!p->rchild)//如果右兄弟那棵树是空的
        {
            q = p->lchild;//这里跳过了当前节点,走向了下一个左子女
            p->data = q->data;//这里把下一个节点的数据存到了当前节点
    
            p->rchild = q->rchild;//这里把下一个节点的左右子女兄弟均转移到了当前节点上,
            p->lchild = q->lchild;//即把下面的那一棵树变成了当前的节点
    
            free(q);//转移完成,删除"中转站"
        }
        else if(!p->lchild)//如果左子女那棵树是空的,这里的操作和上面没有区别,只是对左右的树的操作反过来了
        {
            q = p->rchild;
            p->data = q->data;
            p->rchild = q->rchild;
            p->lchild = q->lchild;
            free(q);
        }
        else if(p->lchild&&p->rchild)//如果左右都有树的情况下,这种情况比较复杂
        {
            q = p;//复制p到q,这样会得到两个一模一样的树(当前节点的下面整体)
            s = p->lchild;//s作为“中转站”,得到p左子女的那棵树
            while (s->rchild)//遍历p左子女的右边兄弟(较大的数字)当然也可以说是第一个后继
            {
                q = s;//把q更新为p左子女的后继的前一个节点
                s = s->rchild;//持续循环的条件
            }
            p->data = s->data;//把p的关键字更新为它的左子女的第一个后继
            if (q != p)//这里代表成功更新,也就是p的左子女有了后继
            {
                q->rchild = p->lchild;//这里的操作比较复杂:首先由于现在的大节点的数据更新为原先的左子女的第一个后继,那么他就可以访问此后继的左子女
            }//由于是第一个后继,那么一定没有右兄弟了!
            else//更新失败,那么回到第二种右兄弟为空树的情况(好麻烦==||)
            {
                q->lchild = s->lchild;
            }
            free(s);//最后依然是需要删除新建的树呢~
        }
    }
    
    void DeleteTree(BiTree &T,int data)
    {
        if(!T) cout << "can't delete this data!" << endl;
        else
        {
            if(data==T->data)//如果此(当前找到的节点是要删除的节点)
            {
                Delete(T);
                cout << "delete this data successful!" << endl;
            }
            else if(data< (T->data)) DeleteTree(T->lchild,data);
            else DeleteTree(T->rchild,data);//这里用的遍历的查找方式
        }
    }

    注释已经把我想说的话说完啦~~

    最后我们需要验证一下我们的数据结构是否能够经受考验:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef struct BiNode
    {
        int data;
        BiNode *lchild=NULL;
        BiNode *rchild=NULL;
    }BiNode,*BiTree;//声明一棵树,以后可以使用
    
    bool SearchTree(BiTree T,int kval,BiTree f,BiTree &tmp)//树的查找,这里思考了很久tmp的作用,最后理解为之后有时会需要这节点来干一些神奇的事情
    {
        if(!T)//如果是空树,那么自然查不到了
        {
            tmp=f;
            return false;
        }
        else
        {
            if(T->data==kval)//如果此节点对应数据恰好吻合,bingo
            {
                tmp=T;
                return true;
            }
            else if(kval< (T->data)) SearchTree(T->lchild,kval,T,tmp);//这里的遍历查找自然不必多说
            else SearchTree(T->rchild,kval,T,tmp);
        }
    }
    
    void CreatTree(BiTree &T,int data)//树的建立,非常简单,不必过多注释
    {
        BiTree tmp;
        if(!SearchTree(T,data,NULL,tmp))//这里是一定需要遍历到最下面的叶节点的,不然插入节点是一件非常麻烦的事情
        {
            BiNode *t=new BiNode;
            t->data=data;
            if(!tmp) T=t;
            else if(data< (tmp->data)) tmp->lchild=t;//若当前数据小于节点,自然插入左边啦
            else tmp->rchild=t;
            cout << "Insert successful" << endl;
        }
        else
            cout << "Insert failed" << endl;
    }
    
    void Display(BiTree T)//先序遍历展示是否插入成功!
    {
        if(T)
        {
            cout << T->data << ' ' ;//递归show结果自然不必多说
            Display(T->lchild);
            Display(T->rchild);
        }
    }
    
    void Delete(BiTree &p)
    {
        BiTree q;
        BiTree s;
        //从树中删除节点p
        //重新连接左右子树
        if(!p->rchild)//如果右兄弟那棵树是空的
        {
            q = p->lchild;//这里跳过了当前节点,走向了下一个左子女
            p->data = q->data;//这里把下一个节点的数据存到了当前节点
    
            p->rchild = q->rchild;//这里把下一个节点的左右子女兄弟均转移到了当前节点上,
            p->lchild = q->lchild;//即把下面的那一棵树变成了当前的节点
    
            free(q);//转移完成,删除"中转站"
        }
        else if(!p->lchild)//如果左子女那棵树是空的,这里的操作和上面没有区别,只是对左右的树的操作反过来了
        {
            q = p->rchild;
            p->data = q->data;
            p->rchild = q->rchild;
            p->lchild = q->lchild;
            free(q);
        }
        else if(p->lchild&&p->rchild)//如果左右都有树的情况下,这种情况比较复杂
        {
            q = p;//复制p到q,这样会得到两个一模一样的树(当前节点的下面整体)
            s = p->lchild;//s作为“中转站”,得到p左子女的那棵树
            while (s->rchild)//遍历p左子女的右边兄弟(较大的数字)当然也可以说是第一个后继
            {
                q = s;//把q更新为p左子女的后继的前一个节点
                s = s->rchild;//持续循环的条件
            }
            p->data = s->data;//把p的关键字更新为它的左子女的第一个后继
            if (q != p)//这里代表成功更新,也就是p的左子女有了后继
            {
                q->rchild = p->lchild;//这里的操作比较复杂:首先由于现在的大节点的数据更新为原先的左子女的第一个后继,那么他就可以访问此后继的左子女
            }//由于是第一个后继,那么一定没有右兄弟了!
            else//更新失败,那么回到第二种右兄弟为空树的情况(好麻烦==||)
            {
                q->lchild = s->lchild;
            }
            free(s);//最后依然是需要删除新建的树呢~
        }
    }
    
    void DeleteTree(BiTree &T,int data)
    {
        if(!T) cout << "can't delete this data!" << endl;
        else
        {
            if(data==T->data)//如果此(当前找到的节点是要删除的节点)
            {
                Delete(T);
                cout << "delete this data successful!" << endl;
            }
            else if(data< (T->data)) DeleteTree(T->lchild,data);
            else DeleteTree(T->rchild,data);//这里用的遍历的查找方式
        }
    }
    
    int main()
    {
        BiTree T=NULL;
        cout << "Please input the number n of the Bstree:" ;
        int n;
        cin>> n;
        int data;
        cout << "Please input the key of the Bstree" << endl;
        for(int i=0;i<n;i++)
        {
            cin>> data;
            CreatTree(T,data);
        }
        cout << "Please input a key to Search:" ;
        int kval;
        cin>> kval;
        BiTree tmp;
        if(SearchTree(T,kval,NULL,tmp)) cout << "the data is found!" << endl;
        else cout << "can't search this data!" << endl;
        Display(T);
        cout << endl;
        cout << "input the key data you want to delete:" ;
        int key;
        cin>> key;
        DeleteTree(T,key);
        Display(T);
        return 0;
    }

    以上!

    二叉排序树的相关内容就是这些啦~~

    希望大家能够多多指教,本弱鸡知道和大佬们差距很大,希望大家看到博主的错误能够和博主探讨一波,带带本弱鸡一起进步叭!

    感谢大家!

  • 相关阅读:
    试题 基础练习 Sine之舞
    试题 基础练习 Huffuman树
    试题 基础练习 完美的代价
    支付宝支付功能, 创建订单并生成支付链接的接口, 后端支付宝异步回调接口
    GenericAPIView, drf内置的基础分页器和偏移分页器的使用, 过滤器, django-filter插件实现区间分类
    celery, 数据库分表
    redis
    注册, 校验手机号接口, 手机号注册接口, 全局配置drf-jwt身份认证, 局部配置SendSmsAPIView类的频率认证, Vue操作Cookie
    登录, 发送验证码接口, 对官方提供的短信SDK进行二次封装
    pycharm全局搜索快捷键: ctrl + n, Xadmin的使用, 前端Banner小组件, 后端控制轮播图展示的数量, git
  • 原文地址:https://www.cnblogs.com/ever17/p/10920020.html
Copyright © 2011-2022 走看看