Java实现二叉树(一):二叉查找树的实现

Java实现二叉树(一):二叉查找树的实现

    数据结构+算法=程序,这是共识,是真理,还是学生时代卷子中的考点。但大多数程序员往往缺乏数据结构和算法的知识,或是根本没有学过,或是学过,但在工作时频繁地与业务逻辑打交道,也就逐渐忘记了有这么一回事。

    话不多说,直接开始吧,本文将介绍二叉树的基本概念,以及平衡二叉树增删改查节点的实现,由于网上关于数据结构的资料,C++相对居多,而本身作为一个Javaer,将会重新用Java实现一个平衡二叉树,仅供参考,欢迎纠正。

    一、二叉树的基本概念

    定义:二叉树,是一种特殊的树,二叉树的任意一个节点的度都不大于2,不包含度的节点称之为叶子。

    分类:二叉树又有细分,满二叉树,以及完全二叉树,等等等等,本文就不一一介绍了,本文暂且只介绍二叉查找树,也是最常见的一种数据类型。

    遍历方式:二叉树的遍历方式有三种,中序遍历,先序遍历,后序遍历:

  1. 中序遍历:先遍历左子树,再遍历父亲节点,最后遍历右子树;
  2. 先序遍历:先遍历父亲节点,再遍历左子树,最后遍历右子树;
  3. 后序遍历:先遍历父亲节点,再遍历右子树,最后遍历左子树;

    光说显得乏味,我们列一道经典的二叉树题目吧,已知一棵二叉树,如果先序遍历的节点顺序是:ADCEFGHB ,中序遍历是:CDFEGHAB ,则后序遍历结果为:

    这道题需要实现一个二叉树重构,个人的解题思路是这样的–首先,先序遍历最早遍历父亲节点,也就意味着A是根节点,而中序遍历最晚遍历右子树,所以我们可以推测出A的右边也就是B,是A的右子树。于是我们将两个去除AB,也就是DCEFGH,CDFEGH,推测父节点左子树的结果,同样的方法我们可以确定D是左子树的父亲节点,C是左子树的唯一一个左子树,从而继续去除。

    说着好像十分复杂,但是其实就是一个递归的思想,不断的确认父节点,以及拆分左子树,和右子树,完成树的重构,最后得到的树是下图:

《Java实现二叉树(一):二叉查找树的实现》

    如此也就不难得出该树的后序遍历结果为:CFHGEDBA。

    二、二叉查找树的基本概念

    基本概念:二叉查找树是一种特殊的二叉树,要求左子树的全部节点小于父亲节点,右子树的全部节点大于父亲节点,同时,左子树和右子树也为二叉查找树,中序遍历一个二叉查找树,会得到一个有序的元素集合。

    优点:二叉查找树拥有良好的查找性能,查找次数最多为树的深度,这也就意味着二叉查找树的查找效率取决于树的深度,而满二叉树的深度为log(N+1),所以此时他的查找时间复杂度为O(logN)。

    缺点:缺点在优点中说的十分明确了,二叉查找树在极端的情况下会大大降低他的查找性能,比如说,顺序插入节点时,二叉树就会像一个链表,查找的时间复杂度将为O(N)。

    构建一个深度合理的二叉查找树本文暂不做介绍,之后的文章会讲述平衡二叉树的概念,可以关注。

    三、二叉查找树的基本操作和Java实现

    咳咳,注意,笔者要点题了。

    且不说二叉树,任何的数据结构都离不开增删改查四个步骤,就由增删改查分块讲解二叉查找树吧。

    Insert

    要求:二叉查找树插入时,要满足二叉查找树的特性,左小右大。

    实现思路:递归地去遍历一颗树,如果大于节点就遍历节点的右子树,如果小于节点就遍历节点的左子树,当节点为空时插入。

    代码:

//以查找二叉树的规则插入,小的在左,大的在右
    public static void insert(Tree tree,int value)
    {    
        if(tree.getValue() == null)
        {
            tree.setValue(value);
        }
        else if(tree.getValue() < value)
        {
            if(tree.getrChild()!=null)
            {
                insert(tree.getrChild(),value);
            }
            else {
                tree.setrChild(new Tree());
                insert(tree.getrChild(),value);
            }
        }
        else if(tree.getValue() > value)
        {
            if(tree.getlChild()!=null)
            {
                insert(tree.getlChild(),value);
            }
            else {
                tree.setlChild(new Tree());
                insert(tree.getlChild(),value);
            }
        }
    }

    Delete

    要求:删除的要求相对复杂,因为,删除之后,还是必须要保持二叉查找树的特性。

    实现思路:先找到要删除的节点,这时会有三种情况:

  1. 删除节点为(左或者右)叶子–此时直接将节点的父亲节点的(左或者右)节点置为空;
  2. 删除节点只包含一个(左或者右)子树–此时,判断删除节点是父节点的左子树,还是右子树,用删除节点的唯一那一个“儿子”去替换自己;
  3. 删除节点既有左子树又有右子树–此时,可以找到右子树中最小的节点,替换删除节点,再递归地删除了它(做法不唯一,同样也可以用左子树中最大的节点去替换删除节点);

    代码(有些长…):

public static void delete(Tree tree,int value)
    {
        //查询到的节点的父节点
        Tree parent = null;
        //查询到的节点
        Tree searchTree = tree;
        //当节点不为空的时候开始循环
        while(searchTree != null)
        {
            //如果节点的值等于查询值,跳出循环,之后的逻辑就是一个树查找的逻辑
            if(searchTree.getValue()==value)
            {
                break;
            }
            else if(searchTree.getValue() > value)
            {
                parent = searchTree;
                searchTree = searchTree.getlChild();
            }
            else if(searchTree.getValue() < value)
            {
                parent = searchTree;
                searchTree = searchTree.getrChild();
            }
        }

        //查询到的节点是父节点的左节点与否
        boolean parentLeftFlag = false;
        //查询到的节点的父节点是否存在
        boolean hasParent = true;
        //查询到的节点的左子树存在与否
        boolean leftFlag = false;
        //查询到的节点的右子树存在与否
        boolean rightFlag = false;


        if(parent == null)
        {
            hasParent = false;
        }
        else if(parent.getrChild() == searchTree)
        {
            parentLeftFlag = false;
        }
        else{
            parentLeftFlag = true;
        }


        if(searchTree == null)
        {
            return;
        }
        if(searchTree.getlChild() != null){
            leftFlag = true;
        }
        if(searchTree.getrChild() != null){
            rightFlag = true;
        }

        if(!leftFlag && !rightFlag)
        {
            if(hasParent)
            {
                if(parentLeftFlag)
                {
                    parent.setlChild(null);
                }
                else{
                    parent.setrChild(null);
                }
            }
            else{
                searchTree.setValue(null);
            }
        }
        else if(!leftFlag || !rightFlag)
        {
            if(hasParent)
            {
                if(parentLeftFlag)
                {
                    if(leftFlag)
                    {
                        parent.setlChild(searchTree.getlChild());
                    }
                    else {
                        parent.setlChild(searchTree.getrChild());
                    }
                }
                else {
                    if(leftFlag)
                    {
                        parent.setrChild(searchTree.getlChild());
                    }
                    else {
                        parent.setrChild(searchTree.getrChild());
                    }
                }
            }
            else
            {
                if(leftFlag){
                    searchTree.setValue(searchTree.getlChild().getValue());
                    searchTree.setrChild(searchTree.getlChild().getrChild());
                    searchTree.setlChild(searchTree.getlChild().getlChild());
                }
                else
                {
                    searchTree.setValue(searchTree.getrChild().getValue());
                    searchTree.setlChild(searchTree.getrChild().getlChild());
                    searchTree.setrChild(searchTree.getrChild().getrChild());
                }
            }
        }
        else if(leftFlag && rightFlag){
            Tree minTree = searchTree.getrChild();
            while (minTree.getlChild() != null)
            {
                minTree = minTree.getlChild();
            }
            Integer minTreeValue = minTree.getValue();
            delete(searchTree,minTreeValue);
            searchTree.setValue(minTreeValue);
        }
    }

    Update

    要求:修改的话其实就是查找并修改,并无太大要求。

    实现思路:当大于节点查找右子树,当小于节点查找左子树,当等于节点,返回节点。

    代码(此处仅仅放出查找的代码):

public static Tree findNode(Tree tree,int value)
    {
        if(tree.getValue() == value)
        {
            return tree;
        }
        else if(tree.getValue() < value){
            if(tree.getrChild() != null)
            {
                return findNode(tree.getrChild(),value);
            }
            else {
                return null;
            }
        }
        else if(tree.getValue() > value){
            if(tree.getlChild() != null)
            {
                return findNode(tree.getlChild(),value);
            }
            else {
                return null;
            }
        }
        return null;
    }

    Read

    上文已经把查找的代码和实现思路写出来了,这里写一下中序遍历的两种实现方式和思路吧(先序遍历和后序遍历大同小异,由于是二叉查找树,只实现中序遍历)。

    第一种方式–递归方式:

    实现思路:先递归的遍历左子树,再输出父节点的值,最后再递归地遍历右子树,跳出递归条件:当左子树或者右子树不为空时。

    代码:

//中序遍历
    public static void middleSearch(Tree tree)
    {

        if(tree.getlChild()!=null)
        {middleSearch(tree.getlChild());}
        System.out.print(tree.getValue());
        if(tree.getrChild()!=null)
        {middleSearch(tree.getrChild());}
    }

    第二种方式–非递归方式:

    实现思路:手动的建立一个栈,循环地去轮询左子树,直到左子树为空,将还未输出的父亲的节点依次压入栈中,当轮询到空树时,开始弹出栈内的节点,输出节点的值,并将节点的右子树作为轮询的值压入栈。

    代码:

//使用非递归方式中序遍历
    public static void midddleSearchUseStack(Tree tree)
    {
        Stack<Tree> stack = new Stack<>();
        Tree rollTree = tree;
        while(rollTree != null || stack.size() != 0)
        {
            if(rollTree != null)
            {
                stack.push(rollTree);
                rollTree = rollTree.getlChild();
            }else
            {
                rollTree = stack.pop();
                System.out.print(rollTree.getValue());
                rollTree = rollTree.getrChild();
            }
        }

    }

    总结:其实两种方式大致无差别,其实递归也是使用方法栈的方式将每一个方法压入栈中,时间复杂度两者没有差异。但是考虑到方法栈的深度限制,和方法栈更多的内存开销(方法栈会将局部变量等信息压入栈中),还有方法调用的开销,显然非递归方式效率是更加高的。但是递归方式有个优点,可读性高,简单,易实现。

    好了,就到这,本文的Java代码并没有全量的放在博客中,笔者将实现的全部代码上传到了GitHub上,需要的同学可以上去下载(记得给星哦)。

    GitHub下载地址:https://github.com/liufangqi/treeTestRealOne

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