数据结构——树

树形结构是一种非常重要的非线性的数据结构。树结构与线性结构不同之处:线性结构中任意一个元素最多只有一个后继元素,而树形结构中每个元素可以有多个后继;线性结构和树形结构中每个元素最多只有一个前驱元素。这篇文章为本人原创,谢绝转载。具体的内容目录如下:
一、树的属性
二、二叉树
三、二叉树与树、森林的转换
四、二叉排序树
五、平衡二叉树

一、树的属性

树又一个特定的称为根的节点,这个节点无前驱节点。树可以用递归的方式来定义,也就是说树是由若干个子树构成。如下图所示,A是根节点,没有前驱节点,B、C是A的子节点,也是一棵树。这里要简单说下树的一些术语。
1.节点:表示树中的一个数据元素。如下图中,A、B、C、D、E、F、G、H、I都是是节点。
2.孩子节点:表示树中节点的直接后驱节点。如下图中,A的孩子节点为B、C。
3.双亲节点:表示树中节点的直接前驱节点,如下图中,D、E、F的双亲节点是B。
4.兄弟节点:表示具有相同双亲节点的节点。如下图中,D、E、F是兄弟节点。
5.祖先节点:表示从根节点到该节点的双亲节点都是祖先节点。如下图中,I的祖先节点为A、B、E。
6.子孙节点:表示该节点下所有孩子节点,包括子节点的子节点。如下图中,A的子孙节点为B、C、D、E、F、G、H、I。
7.节点的度:该节点的子树的个数,可以理解为该节点后代的代数。如下图中,A的度为3,D的度为0。
8.叶子节点:度为0的节点,也就是没有后驱节点。如下图中,D、H、I、G都是叶子节点。
9.分支节点:度不为0的节点,可以理解为树枝,有后驱节点。B、C、E都是分支节点。
10.树的层数:树的根节点所在的层树为1,其他节点的层数等于他的双亲节点的层数+1。如下图中,A树的层数为4,B树的层数为3。
11.树的深度:表示整棵树的最大层数,也叫高度。如下图中,深度为4。
12.森林:零棵树或有限棵树互不相交的树的集合叫森林。如下图中,将A节点去掉,B树与C树可以构成森林。
13.有序树与无序树:树中节点的子树从左到右是有次序的称为有序树,否则为无序树。


树结构

二、二叉树

二叉树是一颗每个节点有不超过两个孩子节点的树。两颗子树有左右之分,称为左子树和右子树,左子树和右子树都可以为空,如果不为空时,子树也是一颗二叉树。二叉树也有一种递归的概念。二叉树有五种基本形态
1.空树:根节点为空的二叉树
2.只有根节点:只有根节点,根节点没有左右子树
3.只有左子树:只有左子树,没有右子树
4.只有右子树:只有右子树,没有左子树
5.有左右子树:该节点同时有左右子树

二叉树的性质
1.二叉树第i(i>=1)层上最多有2^(i-1)个节点。
2.深度为k(k>=1)的二叉树最陡有2^k-1个节点。
3.满二叉树:深度为k并且含有2k-1个节点的二叉树。可以理解为,树枝节点都有左右子树。通俗地讲,在不增加树的深度的前提下,无法再多添加一个节点的二叉树称为满二叉树。

满二叉树

4.完全二叉树:深度为k,有n个节点的二叉树,当且仅当每个节点的编号与相应满二叉树节点顺序编号从1~n相对应时,则称为完全二叉树。通俗地讲,完全二叉树相当于删除满二叉树的最底层最右边的连续若干个节点。
完全二叉树

二叉树的存储结构
1.顺序存储结构
将二叉树中所有节点元素存储到一维数组中,这是最简单的顺序存储结构。设一维数组list,list[0]空置不用,从第一个开始,每个数组元素存储树的每一个节点元素。按照完全二叉树编号从上到下,从左到右的顺序将每个节点元素存储到数组中。如下图所示,第i个节点的双亲节点为i/2,左右孩子节点分别为2i、2i+1。

顺序储存结构

如果二叉树是一颗单边二叉树(只有左子树或只有右子树),对于每个二叉树而言,有插入新元素的可以,所以需要保留存储空间。如下图中,红色表示可以插入新元素,用虚线连接。

单边二叉树

下图表示单边二叉树的顺序存储结构,本来只有四个元素,却要分配8个存储空间。如果一颗深度为k的单边二叉树,至少要2^k个存储空间,则有2^k-k个空间为空,这样造成资源浪费。这种存储结构适合于满二叉树和完全二叉树,一般的二叉树很少采用这种存储结构。
单边二叉树顺序存储结构

2.链表存储结构
二叉树的链表结构有常见的两种:二叉链表、三叉链表。

  • 二叉链表:包含一个数据元素,左子树指针,右子树指针。可以很容易找到该节点的子孙节点,但是不容易找到其双亲节点。
  • 三叉链表:包含一个数据元素,左子树指针,右子树指针,还有一个双亲节点指针,方便找出其双亲节点。


    存储结构

一般树的存储结构
1.双亲表示法:存储父节点的序号,方便于查找父节点。下图中的左边为一般的树形结构,右边为存储结构。

双亲表示法

2.孩子表示法:存储孩子节点的指针,方便于查找孩子节点。下图中的左边为一般的树形结构,右边为存储结构。


孩子表示法

3.双亲孩子表示法:结合上述两种方式。可方便查找双亲和孩子节点。


双亲孩子表示法

4.二叉树表示法:将一个普通树转换为二叉树,具体规则如下:
-左指针域指向该节点的第一个子节点;
-右指针域指向该节点的第一个兄弟节点;


二叉树表示法

二叉树的遍历
遍历二叉树就是以一定的顺序访问每个节点,并且每个节点只能被访问一次。
假设有一颗二叉树有7个节点,为了方便理解,为每个没有同时存在左右子树的节点补充一个空的节点。如下图所示,图中的红色表示补充的空的节点,外围虚线表示搜索路径。

搜索路径

按照从左到右的方式,可以分为三种遍历方式
注意:递归算法实现简单,但效率较低,这是因为系统需要维护一个工作栈以保证递归方法的正确执行,所有在实际使用中推荐非递归方式。
1.先序遍历:先访问根节点,再访问左子树,再访问右子树。如上图中,第一次搜索经过的节点为A、B、D、G、C、E、F。具体的算法如下:

    /**
     * 二叉树的先序遍历(非递归)
     * @param root 根节点
     */
    public static void fristOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        WLTreeNode node = root;
        //将所有左孩子压栈
        while (node != null || stack.size() > 0) {
            if(node != null) {
                printTreeNode(node);
                stack.push(node);
                node = node.getLeft();
            }else {
                node = stack.pop();
                node = node.getRight();
            }
        }
    }

    /**
     * 二叉树的先序遍历
     * @param root 根节点
     */
    public static void firstOrderTraversal(WLTreeNode root) {
        if(root != null) {
            printTreeNode(root);
            if(root.getLeft() != null) {
                firstOrderTraversal(root.getLeft());
            }
            if(root.getRight() != null) {
                firstOrderTraversal(root.getRight());
            }
        }
    }

2.中序遍历:先访问左子树,再访问根节点,再访问右子树。如上图中,第二次搜索经过的节点为G、D、B、A、E、C、F。具体的算法如下:

    /**
     * 二叉树的中序遍历(非递归)
     * @param root 根节点
     */
    public static void inOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        WLTreeNode node = root;
        while (node != null || stack.size() > 0) {
            if(node != null) {
                stack.push(node);
                node = node.getLeft();
            }else {
                node = stack.pop();
                printTreeNode(node);
                node = node.getRight();
            }
        }
    }
    /**
     * 二叉树的中序遍历
     * @param root 根节点
     */
    public static void inOrderTraversal(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() != null) {
                inOrderTraversal(root.getLeft());
            }
            printTreeNode(root);
            if(root.getRight() != null) {
                inOrderTraversal(root.getRight());
            }
        }
    }

3.后序遍历:先访问左子树,在访问右子树,在访问根节点。如上图中,第三次搜索经过的节点为G、D、B、E、F、C、A。具体的算法如下:

/**
     * 二叉树的后序遍历(非递归)
     * @param root 根节点
     */
    public static void postOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        Stack<WLTreeNode> output = new Stack<>();
        WLTreeNode node = root;
        while (node != null || stack.size() > 0) {
            if(node != null) {
                stack.push(node);
                output.push(node);
                node = node.getRight();
            }else {
                node = stack.pop();
                node = node.getLeft();
            }
        }
        while (output.size() > 0) {
            printTreeNode(output.pop());
        }
    }
    /**
     * 二叉树的后序遍历
     * @param root 根节点
     */
    public static void postOrderTraversal(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() != null) {
                postOrderTraversal(root.getLeft());
            }
            if(root.getRight() != null) {
                postOrderTraversal(root.getRight());
            }
            printTreeNode(root);
        }
    }

二叉树的其他操作

    /**
     * 按层遍历二叉树
     * 1.将二叉树根节点入队列
     * 2.将队头节点出队列,并判断此节点算法有左右孩子,如果有,则将其左右孩子入队列
     * @param root 根节点
     */
    public static void levelTraversal(WLTreeNode root) {
        if(root != null) {
            List<WLTreeNode> list = new ArrayList<>();
            //1.根节点入队列
            list.add(root);
            while (list.size() > 0) {
                //取出队头节点
                WLTreeNode node = list.get(0);
                printTreeNode(node);
                
                list.remove(0);
                //判断是否有左右孩子
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
            }
        }
    }

    /**
     * 按层遍历二叉树 
     * @param root 根节点
     * @param index 第几个节点,从0开始
     * @return 节点
     */
    public static WLTreeNode findNodeByIndex(WLTreeNode root,int index) {
        if(root != null && index >= 0) {
            List<WLTreeNode> list = new ArrayList<>();
            //根节点入队列
            list.add(root);
            while (list.size() > 0) {
                //取出队头节点
                WLTreeNode node = list.get(0);
                //==0,则找到
                if(index == 0) {
                    return node;
                }
                //弹出队头节点
                list.remove(0);
                index --;
                
                //如果该节点有左右节点,则入队列
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
            }
        }
        return null;
    }

    /**
     * 二叉树的深度
     * @param root 根节点
     * @return 深度值 根节点深度为1
     */
    public static int depthOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() == null && root.getRight() == null) {
                return 1;
            }
            int leftDepth = depthOfBinaryTree(root.getLeft());
            int rightDepth = depthOfBinaryTree(root.getRight());
            return Math.max(leftDepth, rightDepth)+1;   
        }
        return 0;
    }
    
    /**
     * 二叉树的宽度是指二叉树各层结点个数的最大值
     * @param root 根节点
     * @return 深度值 根节点深度为1
     */
    public static int widthOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            List<WLTreeNode>list = new ArrayList<>();
            list.add(root);
            int maxWidth = 1;//已有根节点
            int curWidth = 0;
            while (list.size() > 0) {
                curWidth = list.size();
                if(maxWidth < curWidth) {
                    maxWidth = curWidth;
                }
                
                for (int i = 0; i < curWidth; i++) {
                    WLTreeNode node = list.get(0);
                    list.remove(0);
                    if(node.getLeft() != null) {
                        list.add(node.getLeft());
                    }
                    if(node.getRight() != null) {
                        list.add(node.getRight());
                    }
                }
            }
            return maxWidth;
        }
        return 0;
    }
    
    
    /**
     * 二叉树的节点个数
     * @param root 根节点
     * @return 全部节点个数
     */
    public static int numberOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            int count = 0;
            List<WLTreeNode>list = new ArrayList<>();
            list.add(root);
            while (list.size() > 0) {
                WLTreeNode node = list.get(0);
                list.remove(0);
                
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
                count++;
            }
            return count;
        }
        return 0;
    }
    
    /**
     * 二叉树某一层的全部节点个数
     * @param root 根节点
     * @return 节点个数
     */
    public static int numberOfNodeOnLevel(WLTreeNode root,int level) {
        if(root != null && level >= 0) {
            //根节点只有一个节点
            if(level == 1) {
                return 1;
            }
            return numberOfNodeOnLevel(root.getLeft(), level-1) + numberOfNodeOnLevel(root.getRight(), level-1);
        }
        return 0;
    }
    
    /**
     * 二叉树的叶子节点个数
     * @param root 根节点
     * @return
     */
    public static int numberOfLeafNode(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() == null && root.getRight() == null) {
                return 1;
            }
            return numberOfLeafNode(root.getLeft()) + numberOfLeafNode(root.getRight());
        }
        return 0;
    }
    
    /**
     * 二叉树中某个节点到根节点的路径
     * 1.将根节点压入栈,再从左子树中查找,如果没找到,再从右子树中查找,如果没找到,则弹出根节点,再遍历栈中上一个节点
     * 2.如果找到,则栈中存放的节点就是路径经过的节点
     * @param root 根节点
     * @param node 起点节点
     * @return 路径数组
     */
    public static List<WLTreeNode> pathOfTreeNode(WLTreeNode root,WLTreeNode node){
        List<WLTreeNode>list = new ArrayList<>();
        boolean isFound = isFoundTreeNode(root, node, list);
        System.out.println("isFound = "+isFound);
        return list;
    }
    
    /**
     * 查找二叉树中是否包含该节点
     * @param root 根节点 
     * @param node 待查找节点
     * @param list 路径数组
     * @return 是否找到
     */
    public static boolean isFoundTreeNode(WLTreeNode root,WLTreeNode node,List<WLTreeNode>list) {

        if(root == null || node == null) {
            return false;
        }
        
        //判断是否为当前节点
        if(root.getValue() == node.getValue()) {
            return true;
        }
        System.out.println("find root value = "+root.getValue());
        //将根节点压入栈
        list.add(root);
        //先从左子树中查找
        boolean isFound = isFoundTreeNode(root.getLeft(),node,list);
        if(!isFound) {
            //左子树中没找到,则从右子树中查找
            isFound = isFoundTreeNode(root.getRight(),node,list);
        }
        //左右子树中都没找到,则弹出此根节点 
        if(!isFound) {
            list.remove(list.size()-1);
        }
        return isFound;
    }
    
    /**
     * 是否为满二叉树
     * 除了叶子节点,每个节点都有左右子节点
     * @param root 根节点
     * @return 
     */
    public static boolean isFullBinaryTree(WLTreeNode root) {
        if(root != null) {
            int depth = depthOfBinaryTree(root);
            int nodeCount = numberOfBinaryTree(root);
            if(nodeCount == Math.pow(2, depth)-1) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 是否为平衡二叉树
     * 定义:一颗空树或左右子树的高度差的绝对值不超过1,并且左右子树都是一个平衡二叉树
     * @param root 根节点
     * @return
     */
    public static boolean isAVLBinaryTree(WLTreeNode root) {
        //一颗空树
        if(root == null) {
            avlHeight = 0;
            return false;
        }
        //只有根节点
        if(root.getLeft() == null && root.getRight() == null) {
            avlHeight = 1;
            return true;
        }
        
        boolean isLeft = isAVLBinaryTree(root.getLeft());
        int leftHeight = avlHeight;
        boolean isRight = isAVLBinaryTree(root.getRight());
        int rightHeight = avlHeight;
        
        avlHeight = Math.max(leftHeight, rightHeight);
        if(isLeft && isRight && Math.abs(leftHeight-rightHeight) <= 1) {
            return true;
        }
        return false;
    }
    
    
    /**
     * 反转二叉树 非递归
     * @param root
     * @return
     */
    public static WLTreeNode invertBinaryTreeByQueue(WLTreeNode root) {
        if(root == null) {
            return root;
        }
        List<WLTreeNode>list = new ArrayList<>();
        list.add(root);
        while (list.size() > 0) {
            WLTreeNode node = list.get(0);
            list.remove(0);
            
            WLTreeNode tmpNode = node.getLeft();
            node.setLeft(node.getRight());
            node.setRight(tmpNode);
            
            if(node.left != null) {
                list.add(node.getLeft());
            }
            if(node.right != null) {
                list.add(node.getRight());
            }
        }
        
        return root;
    }
    /**
     * 打印树形结构
     */
    private static void printTreeNode(WLTreeNode node) {
        System.out.println(node.getValue());
    }

三、二叉树与树、森林的转换

1.树转换为二叉树
(1)加线:在树中所有相邻的兄弟节点之间加一条线。
(2)抹线:对树中的每个节点,只保留与第一个孩子节点之间的连线,删除与其他孩子节点之间的连线。
(3)调整:以每个节点为轴心,将其右侧所有节点按顺时针转动45度,使之称为一颗二叉树。


树转换为二叉树

2.二叉树转换为树
树与二叉树之间的转换是可逆的,将二叉树转换为树也分为三步:
(1)加线:若某个节点是其双亲节点的左孩子,则把该节点的所有右子孙节点都与该节点的双亲节点用线连起来。
(2)抹线:删除二叉树中所有双亲节点与右孩子节点之间的连线。
(3)调整:把虚线改为实线,把节点按层次排列。


二叉树转换为树

3.森林转换为二叉树
森林是树的集合,树可以和二叉树相互转换,森林也可以,只不过要麻烦一点点。
(1)转换:将森林中的每棵子树转换为二叉树,给每棵子树编号为1,2,3,......n棵二叉树。
(2)加线:在所有的二叉树的根节点之间加一条线,也就是从第二棵二叉树开始,将第i+1棵二叉树作为第i棵树的右子树。


森林转换为二叉树

4.二叉树转换为森林
(1)抹线:从二叉树的根节点开始搜索所有的右子孙节点,将其之间的连线抹去,这样就得到包含若干棵二叉树的森林。
(2)还原:将每棵二叉树还原为一般的树。


二叉树转换为森林

四、二叉排序树

二叉排序树,顾名思义就是用二叉树实现排序功能,是一种特殊的二叉树,需要满足一下性质
1.若左子树不为空,则左子树中所有节点的数据值均小于根节点的数据值。
2.若右子树不为空,则右子树中所有节点的数据值均大于根接待你的数据值。
3.左子树和右子树也分别是二叉排序树。
面试的时候可能会遇到这样的问题,给出一组数字,按照顺序插入,画出二叉排序树的结构图。下面举个例子,画出10,20,16,6,4,8,30,55这组数字的二叉排序树的结构。这里要说明一下插入的原理,插入的元素比上一个元素大,则插入在元素的右边,否则插入在元素的左边。


二叉排序树添加过程

二叉排序树的插入
二叉排序树是动态查找,动态插入的树表,不是一次完成的。在查找的过程中,当书中不存在待插入的值时才进行插入操作。新插入的节点一定是叶子节点。

    /**
     * 二叉排序树的插入节点操作
     * @param root 树的根节点
     * @param value 插入值
     * @return 插入的新节点
     */
    public static WLTreeNode binaryTreeInsertion(WLTreeNode root,int value) {
        //创建新节点
        WLTreeNode node = new WLTreeNode(null, null, value);
        if(root == null) {
            return node;
        }
        
        WLTreeNode pNode = root;
        WLTreeNode fNode = null;//找出新节点的直接父节点
        
        //遍历二叉树,查找新节点添加位置
        while (pNode != null) {
            fNode = pNode;
            if (value < pNode.getValue()) {
                pNode = pNode.getLeft();
            }else if(value > pNode.getValue()){
                pNode = pNode.getRight();
            }else {
                break;
            }
        }
        if(value < fNode.getValue()) {
            fNode.setLeft(node);
        }else if(value > fNode.getValue()){
            fNode.setRight(node);
        }
        return node;
    }

二叉排序树的查找
二叉排序树的查找就是遍历每个节点,比较关键字与每个节点的值是否相等,如果相等,则查找成功;否则在根节点的左子树或右子树中继续查找,这是一个递归的过程。
(1)如果二叉排序树为空,则查找失败,返回空指针。
(2)如果查找关键字和根节点值相等,则查找成功,返回根节点指针。
(3)如果查找关键字小于根节点值,则在根节点的左子树中查找。
(4)如果查找关键字大于根节点值,则在根节点的右子树中查找。

    /**
     * 二叉排序树的查找
     * @param root 根节点
     * @param x 关键字
     * @return 关键字节点
     */
    public static WLTreeNode binaryTreeSearch(WLTreeNode root,int x) {
        if(root == null) {
            return null;
        }else if (x == root.getValue()) {
            return root;
        }else if (x < root.getValue()) {
            return binaryTreeSearch(root.getLeft(), x);
        }else {
            return binaryTreeSearch(root.getRight(), x);
        }
    }
    /**
     * 二叉排序树的查找 非递归
     * @param root 根节点
     * @param x 关键字
     * @return 关键字节点
     */
    public static WLTreeNode binaryTreeSearchByStack(WLTreeNode root,int x) {
        if(root != null) {
            Stack<WLTreeNode> stack = new Stack<>();
            while (root != null) {
                if(root.getValue() == x) {
                    return root;
                }else if (root.getValue() > x) {
                    stack.push(root);
                    root = root.getLeft();
                }else {
                    stack.push(root);
                    root = root.getRight();
                }
            }
        }
        return null;
    }

二叉排序树的删除
二叉排序树的删除操作是在查找的基础上进行的,也就是在二叉排序树中查找是否存在待删除关键字的节点,如果存在,则删除。二叉排序树的删除节点比较麻烦,可以分为一下四种情况:
(1)待删除节点为叶子节点,则直接删除

删除叶子节点

(2)待删除节点只有左子树,没有右子树,则用左子树代替该节点


删除左子树

(3)待删除节点只有右子树,没有左子树,则用右子树代替该节点


删除右子树

(4)待删除节点有左右子树,找出右子树中最小值的节点,将最小值节点替换该节点,


删除左右子树

具体的实现方法如下:

    /**
     * 二叉排序树删除节点操作
     * 1.如果节点为叶子节点,直接删除
     * 2.如果节点只有左子树,无右子树,则用左子树代替该节点
     * 3.如果节点只有右子树,无左子树,则用右子树代替该节点
     * 4.如果节点有左、右子树,则找出其右子树最小值节点代替该节点
     * @param root 树的根节点
     * @param value 删除值
     * @return 删除后的树
     */
    public static WLTreeNode binaryTreeDelete(WLTreeNode root,int value) {
        if(root != null) {
            WLTreeNode pNode = root;//待删除节点
            WLTreeNode fNode = null;//fNode为pNode的直接父节点
            //找出pNode节点和pNode的直接父节点
            while(pNode != null) {
                if(value == pNode.getValue()) {
                    break;
                }else if (value < pNode.getValue()) {
                    fNode = pNode;
                    pNode = pNode.getLeft();
                }else {
                    fNode = pNode;
                    pNode = pNode.getRight();
                }
            }
            
            //没有该节点
            if(pNode == null) {
                return root;
            }
            
            if(pNode.getLeft() == null && pNode.getRight() == null) {//待删除节点为叶子节点
                //判断是否为根节点
                if(fNode != null) {
                    if (pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getLeft());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getRight());
                    }
                    fNode = null;
                }else {
                    root = null;
                }
                pNode = null;
            }else if (pNode.getLeft() != null && pNode.getRight() == null) {//待删除节点只有左子树,没有右子树 
                if(fNode != null) {
                    if(pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getLeft());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getLeft());
                    }
                    fNode = null;
                }else {
                    root = root.getLeft();
                }
                pNode = null;
            }else if (pNode.getLeft() == null && pNode.getRight() != null) {//待删除节点只有右子树,没有左子树
                if(fNode != null) {
                    if(pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getRight());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getRight());
                    }
                }else {
                    root = root.getRight();
                }
            }else {//待删除节点同时有左右子树
                
                //1.查找待删除节点的直接后驱节点,也就是该节点右子树中最小值节点qNode
                //2.将qNode节点替换待删除节点
                
                WLTreeNode sNode = pNode.getRight();//直接后驱节点
                WLTreeNode qNode = sNode;//直接后驱节点的直接父节点
                while (sNode.getLeft() != null) {
                    qNode = sNode;
                    sNode = sNode.getLeft();
                }
                
                if(sNode == qNode) {
                    fNode.setRight(qNode);
                    qNode.setLeft(pNode.getLeft());
                }else {
                    qNode.setLeft(sNode.getLeft());
                    fNode.setRight(sNode);
                    sNode.setLeft(pNode.getLeft());
                    sNode.setRight(pNode.getRight());
                }
                
                pNode = null;
                qNode = null;
                fNode = null;
                sNode = null;
            }
            
             return root;
        }
        return root;
    }

五、平衡二叉树

在说平衡二叉树之前,需要先讲讲二叉排序树的查找的时间复杂度。对于含有n个节点的二叉排序树,查找关键字节点是从根节点开始,所需要比较次数就是该节点所在的层数。例如下图中,分别查找关键字10,6,16,55,查找的次数分别为1,2,3,4。假设每个节点的查找概率相等,所以平均查找次数为(1+22+34+4)/8 ≈ 2.6。如果是一颗单边二叉排序树(全部只有左子树或只有右子树),则平均查找次数为(1+2+3+4+6+7+8)/8 = 4.5。
总结一下,在最好的情况下,一颗包含n个节点的二叉排序树的查找复杂度为O(log2(n));在最坏的情况下,复杂度为O(n)。一般的情况下,复杂度介于O(log2(n))到O(n)之间。因此才需要构造一颗平衡的二叉排序树。

二叉排序树复杂度

平衡二叉树的定义
平衡二叉树又叫AVL树,具有以下性质
1.左子树和右子树的深度的差值的绝对值不大于1
2.左子树和右子树都是平衡二叉树
以上的第二点很好理解,第一点怎么理解呢?如下图中,左边是平衡二叉树,右边是非平衡二叉树。图中节点右边是该节点左右子树深度的差值。

AVL树

平衡二叉树的调整
在构建二叉排序树过程中,每插入一个新的节点,先判断插入是否会破坏树的平衡性。如果会,则找出最小不平衡子树,在保持二叉排序树的前提下,调整最小平衡子树中个节点之间的关系。可以根据失衡的原因分为以下四种类型:
(1)LL型调整
假设在A节点左孩子B的左子树上插入新的节点,导致A二叉排序树失去平衡。如下图中,插入值为2的新节点,A节点的值为10,B节点的值为6,此时A树的平衡差值从1变为2,从而失去了平衡。
调整操作:向右顺时针旋转一次,就是将B作为根节点,A作为B的有孩子,同时将B的原来的右子树作为A节点的左子树。
LL型调整

(2)RR型调整
在A节点右孩子B的右子树上插入新的节点,导致A二叉排序树失去平衡。如下图中,插入值为60的新节点,A节点的值为10,B节点的值为20,此时A、B树的平衡差值从1变为2,从而失去了平衡。
调整操作:向左逆时针旋转一次,就是将B作为根节点,A作为B的左孩子,同时B原来的左子树调整为A子树的右子树。


RR型调整

(3)LR型调整
在A节点的左孩子B的右子树C上插入新节点导致二叉排序树A失去平衡。如下图中,插入值为9的新节点,A节点的值为10,B节点的值为5,C节点值为7,此时A、B、C树的平衡差值从1变为2,从而失去平衡。
调整操作:进行两次选转,先顺时针在逆时针。就是将C作为根节点,A作为C的右孩子,B作为C的左孩子,同时调整C原来的两棵子树。将C原来的左子树作为B的右子树,C的右子树作为A的左子树。


LR型调整

(4)RL型调整
在A的右孩子B的左子树C上插入新的节点导致A树失去平衡。如下图中,插入值为9的新节点,A节点的值为10,B节点的值为20,C节点的值为16,此时A、B树的平衡差值从1变为2,C树的平衡差值从0变为1,从而失去平衡。
调整操作:将C节点作为根节点,A作为C的左孩子,B作为C的右孩子,同时调整C原来的两棵子树,将C原来的左子树调整为A的右子树,C原来的右子树调整为B的左孩子。


RL型调整
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342