BST树常见算法的分析、实现与优化

知识点:

1.英文名Binary Search Tree,叫 二叉排序树 ,也叫 二叉搜索树。
2. 二叉搜索树 或者是一棵空树,或者是具有下列性质的二叉树:

  • 每个结点都有一个作为搜索依据的关键码(key),所有结点的关键码互不相同。
  • 左子树(如果存在)上所有结点的关键码都小于根结点的关键码。
  • 右子树(如果存在)上所有结点的关键码都大于根结点的关键码。(即左<根<右)
  • 左子树和右子树也是二叉搜索树。
  • 没有键值相等的结点。

3.如果对一棵二叉搜索树进行中序遍历,可以按从小到大的顺序,将各结点关键码排列起来,所以也称二叉搜索树为二叉排序树。
4.大根堆、小根堆要求其根节点为最大 或 最小。和二叉搜索树是不同的。在这里插入图片描述

结构设计:

class BSTree {
    class BstNode {
        int key;
        //即三叉链表
        BstNode leftchild;
        BstNode parent;
        BstNode rightchild;
       //构造函数
        public BstNode() {
            key = 0;
            leftchild = parent = leftchild = null;
        }
        public BstNode(int x) {
            key = x;
            leftchild = parent = leftchild = null;
        }
        public BstNode(int x,BstNode left,BstNode pa,BstNode right) {
            key = x;
            leftchild = left;
            parent = pa;
            rightchild = right;
        }
    }

    BstNode root;
    BstNode cur;

    public BSTree() {
        root = null;
        cur = null;
    }
}

查询

  • 若找到了值对应的结点就用cur指向,并返回真
  • 二叉搜索树:左 < 根 < 右 (若值比根小,在左边找没有的话,右边更大肯定更没有)

递归实现

 //递归 处理找
    private BstNode Search(BstNode ptr,int kx) {
        if(ptr == null || ptr.key == kx) {
            return ptr;
        }else if(kx < ptr.key) {
            return Search(ptr.leftchild, kx);
        }else {
            return Search(ptr.rightchild, kx);
        }
    }
    //处理结果
    public boolean SearchValue(int kx) {
        boolean res = false;
        cur = Search(root,kx);
        if(cur != null) {
            res = true;
        }
        return res;
    }

非递归实现

 //非递归
    public boolean FindValue(int kx) {
        cur = root;
        boolean res = false;
        //层层往下找
        while(cur != null && cur.key != kx) {
            cur = kx < cur.key ? cur.leftchild : cur.rightchild;
        }
        //处理结果
        if(cur != null && cur.key == kx) {
            res = true;
        }
        return res;
    }

插入

在插入之前,先使用搜索算法在树中检查要插入元素有还是没有。

  • 搜索成功: 树中已有这个元素,不再插入。
  • 搜索不成功: 树中原来没有关键码等于给定值的结点,把新元素加到搜索操作停止的地方。
//    插入
    public boolean Insert(int kx)
    {
        boolean res = true;
        //空树时
        if(root == null)
        {
            root = new BstNode(kx);
            return res;
        }

        cur = root;
        BstNode pa = null;
        //在合适的分支找到底为止
        while(cur != null && cur.key != kx) {
            pa = cur;//找cur的孩子,所以升级当爸了
            cur = kx < cur.key ? cur.leftchild : cur.rightchild;
        }
        //有该值,就不插入(返回false,因为不允许重复)
        if(cur != null && cur.key == kx) {
            res = false;
        }
        else
        {
            cur = new BstNode(kx);
            cur.parent = pa;//子指向父
            //没有该值,就将新结点挂在上面查找的最后一个位置的左或右孩子
            if(cur.key < pa.key)
            {
                pa.leftchild = cur;//父指向子
            }
            else
            {
                pa.rightchild = cur;
            }
        }
        return res;
    }

建树

给出二叉树的顺序存储,通过插入可以从头建立一棵树

       //二叉树顺序存储
        int []ar={53,17,78,9,45,65,87,23,81,94,88,92};
        BSTree myt = new BSTree();
        //用插入方式建立二叉排序树
        for(int i = 0;i<ar.length;++i)
        {
            myt.Insert(ar[i]);
        }

中序遍历

递归遍历代码和B树的一样,很简单。

非递归

  //非递归的中序遍历
    public void NiceInOrder()
    {
        //先合法性判断
        if(root == null)
            return ;
        Stack<BstNode> st = new Stack<BstNode>();
        cur = root;
        while(cur != null || !st.isEmpty())
        {
            while(cur != null)
            {
                st.push(cur);//栈:先进后出
                cur = cur.leftchild;
            }
            // 即把左到底的最后的孩子先输出
            cur = st.pop();
            System.out.print(cur.key+ " ");
            //即左 根 右的遍历顺序
            cur = cur.rightchild;
        }
        System.out.println();
    }

不使用栈或队列

在这里插入图片描述

找中序序列直接后继


    private BstNode First(BstNode ptr) {
        //左到底   即以ptr为根的树的min
        while (ptr != null && ptr.leftchild != null) {
            ptr = ptr.leftchild;
        }
        return ptr;
    }


   //找中序序列的直接后继
    private BstNode Next_InOrder(BstNode ptr)
    {
        if(ptr == null)
            return null;

        if(ptr.rightchild != null)
        {
            return  First(ptr.rightchild);//直接后继为 右子树的最左下结点
        }
        else
        {
            BstNode pa = ptr.parent;
            // 45的后继是53   94后继为空
            while(pa != null && pa.leftchild != ptr)
            {
                ptr = pa;
                pa = ptr.parent;
            }
            return pa;
        }
    }

使用First和Next函数实现中序遍历

 
    private void NiceInOrder_For(){
        for(BstNode p=First(root);p!=null;p=Next_InOrder(p)){
            System.out.print(p.key+" ");

        }
        System.out.println();

    }

非递归中序倒序遍历

找中序序列直接前驱

//找中序序列直接前驱
    private BstNode Last(BstNode ptr) {
        //右到底   相当于Max
        while (ptr != null && ptr.rightchild != null) {
            ptr = ptr.rightchild;
        }
        return ptr;
    }
    private BstNode Prev_InOrder(BstNode ptr) {
        if (ptr == null)
            return null;
        if (ptr.leftchild != null) {
        //左子树的最右下  53的前驱是45
            return Last(ptr.leftchild);
        } else {
            BstNode pa = ptr.parent;
             //9没有前驱    65的前驱是53
            while (pa != null && pa.rightchild != ptr) {
                ptr = pa;
                pa = ptr.parent;
            }
            return pa;
        }
    }

利用Last和Prev实现逆序遍历

   //非递归实现逆向遍历
    private void Reverse_NiceInOrder(){
        //如果有head结点,就可以不用!=null作为结束标记
        for(BstNode p=Last(root);p!=null;p=Prev_InOrder(p)){
            System.out.print(p.key+" ");

        }
        System.out.println();

    }

将二叉排列树 按照中序序列 改成 二叉链表

  • 不给parent属性,将二叉排列树 按照中序序列 改成 二叉链表,
  • 即左孩子是直接前驱,右孩子是直接后继在这里插入图片描述
//    非递归   中序遍历基础上将二叉树改成二叉链表
        public void InOrderList() {
        if (root == null)
            return;
        Stack<BstNode> st = new Stack<BstNode>();
        cur = root;
        BstNode pr = null;
        
        while (cur != null || !st.isEmpty()) {
            //左
            while (cur != null) {
                st.push(cur);
                cur = cur.leftchild;
            }
            //根(即访问的结点)
            cur = st.pop();//一个元素能且只能进栈一次
             //在非递归实现中序遍历时是pop后要将cur打印,这里不需要,只要处理一个当前结点与上一个结点的关系即可
            //即第一次进入while时,此时cur是第一个结点
            if(pr == null)
            {
                root = cur;
                pr = cur;//最近一次访问的结点
            }
            else
            {
                pr.rightchild = cur;//即pr的直接后继 是 这次访问的结点cur
                cur.leftchild = pr;// 这次访问的结点cur的直接前驱 是 pr
                pr = cur;
            }
            //右
            cur = cur.rightchild;
        }
        System.out.println();
    }

    //将上面转为链表的二叉树打印查看
    public void PrintList()
    {
        BstNode p = root;
        //最后一个节点的next没有设置,所以为null
        while(p != null)
        {
            System.out.print(p.key + " ");
            p = p.rightchild; //next
        }
        System.out.println();
    }

判断是不是二叉排序树

非递归

  //非递归   判断是不是二叉排序树(即在中序遍历的基础上,将输出值加个判断)(wps图,带个例子就明白了)
    public boolean Is_BSTree()
    {
        boolean res = true;
        if(root == null)
            return res;
        Stack<BstNode> st = new Stack<BstNode>();
        cur = root;
        BstNode pre = null;//按中序遍历顺序 的 上一个访问的结点,

        //直到找完所有的结点
        while(cur != null || !st.isEmpty())
        {
            //左到底(若cur为空,直接执行下面的pop)
            while(cur != null)
            {
                st.push(cur);//共用一个栈,都往里面压
                cur = cur.leftchild;
            }
            //根  并  判断是否满足  中序遍历依次是由小到大的顺序
            cur = st.pop();//先进后出(第一次弹出叶子节点,肯定 无在他之前弹出来的元素)
            if(pre != null && pre.key >= cur.key)
            {
                res = false;
                break;//一旦发现,立马退出while,接着退出唯一的一层Is_BSTree
            }
            pre = cur;
            // 右
            cur = cur.rightchild;
        }

        return res;
    }

删除数据

  • 在二叉搜索树中删除一个结点时,必须将因删除结点而断开的二叉链表重新链接起来,同时确保二叉搜索树的性质不会失去。
  • 为保证在执行删除后,树的搜索性能不至于降低,还需要防止重新链接后树的高度增加。

所有情况:
1.Root=null
2.未找到数据
3.数据找到了

  • 删除叶结点,只需将其双亲结点指向它的指针清零,再释放它即可。
    在这里插入图片描述

  • 被删结点只有左子树,可以拿它的左子结点顶替它的位置,再释放它(也可以用左子节点的值覆盖被删除结点,然后删子节点,再将子节点的的子树挂在被删除结点下)。
    在这里插入图片描述

  • 被删结点只有右子树,可以拿它的右子结点顶替它的位置,再释放它。
    在这里插入图片描述

  • 被删结点左、右子树都存在,可以在它的右子树中寻找中序遍历的下一个结点(即找直接后继,其实在 左最下),再来处理这个结点的删除问题(若使用直接后继的值覆盖需删除结点,然后删除直接后继即可;若直接后继为根,还需要将直接后继的孩子作为新root;还要将直接后继的子树重挂在直接后继的父节点下)。
    在这里插入图片描述
    (以上几种情况,记前驱 后继都不够直观,记位置左子 右子 或 右子树最左下)

代码

  //左到底
    private BstNode Next(BstNode ptr)
    {
        while(ptr != null && ptr.leftchild != null)
        {
            ptr = ptr.leftchild;
        }
        return ptr;
    }
//    删除指定值(唯一)的结点
    public boolean Remove(int kx)
    {
        boolean res = false;
        //空树
        if(root == null)
            return res;

        BstNode pa = null;
        cur = root;
        //按中序找该节点,有就用cur指向,pa指向其父
        while(cur != null && cur.key != kx)
        {
            pa = cur;
            cur = kx < cur.key ? cur.leftchild:cur.rightchild;
        }
       //没找着
        if(cur == null)
            return res;

        //双分支(即左右孩子都有时)
        if(cur.leftchild != null && cur.rightchild != null)
        {
            BstNode nt = Next(cur.rightchild);//nt为后继结点(即右子树的最左 且 最下的节点)
            cur.key = nt.key;//用后继结点的值 覆盖 要删除的结点值(然后删除后继节点nt即可)
            pa = nt.parent;//后继节点(nt)可能为叶子或有右子树,所以需再处理下 后继节点的父亲(pa)的child
            cur = nt;//cur指向后继结点

        }


       /*
        若进了上面的if,  cur指向原本的后继节点(值被用了,现在他可以作为被删除结点处理了)只能为 叶   或 只有右子树
        若没进if,cur是要删除的结点 且 没有双孩子,只能为 叶子 或 单分支(只有右子树  只有左子树)
        */
        BstNode child = (cur.leftchild != null) ? cur.leftchild:cur.rightchild;
       //待删除结点cur为叶时,上面处理完child为null    cur有右子树时,需要将右子树提上 挂到 后继节点的父亲下
        if(child != null)
            child.parent = pa;            //cur的孩子 指向 cur的父!!!!!!!!!!

       //即待删除结点是根节点,其值提到被删除结点处后,然后结点便要被删除了,此时需要让root指向待删除结点的孩子
        if(pa == null)
        {
            root = child;
        }
        else {
        /*
             cur为叶时,child为null,其父指向child即指向空
             cur为单分支时,将cur的子树 重挂在其父的左或右边
             父 指向 子的子(这下cur不指向谁,也没人指向,为空引用,会被自动垃圾回收)!!!!!!!!!!
             别用child.key<pa.key,因为child有可能为null.
             */
            //即待删除结点在左分支时,把待删除结点的子树 往上提,重新挂在 待删除结点父亲的左边
            if (cur.key < pa.key ) {
                pa.leftchild = child;
            } else {
                pa.rightchild = child;
            }
        }
        return true;
    }
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值