从二叉树到平衡二叉树

193 阅读5分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

二叉树的概念有很多,比如树高度,节点层数,节点度数,路径,叶子节点,分支节点,根节点,父节点,左节点,右节点,兄弟节点,祖先节点,子孙节点,左子树,右子树等基本概念,有些概念是树共有的,有些是只有二叉树才有的,容易理解,就不一一细说。主要说说二叉树的分类、二叉树的四种遍历方式以及实现。

一、二叉树分类

1.满二叉树

​ 二叉树的每一层的节点数都达到了最大值((每层节点的数量最大值为2的n-1次方))。

二叉树.png

2.完全二叉树

​ 假设二叉树的高度为n,除第 n 层外,从第1层到第n-1层的节点数都达到最大个数(前n-1层可以看成一个满二叉树),第n层有叶子节点,并且叶子节点都是从左到右依次排列,这就是完全二叉树。完全二叉树的节点必须满足:某个节点没有左子树那它一定没有右子树,如图 完全二叉树二叉树.png

3.二叉查找树

​ 二叉查找树(Binary Search Tree),也称为二叉搜索树,二叉排序树,它可能是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。二叉查找树的特点:

  • 在二叉查找树中,左子节点的值始终比根结点小,右子节点的值始终比根结点大

  • 任意结点的左右子树也都是二叉查找树

  • 通过中序遍历,将得到的是一个有序的数列

二叉查找树.png 二叉查找树最坏.png

如图所示,第一种情况是最优情况,第二种情况是最坏情况,效率等同于链表。

4.平衡二叉树

​ 平衡二叉树(Balanced Binary Tree),又被称为AVL树(跟AVL算法有区别,不要混淆),它是二叉查找树最优的情况。它很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。平衡二叉树的特点:

  • 必须满足二叉查找树的条件

  • 它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

  • 当删除、新增、修改节点上的值时,它会通过左旋或右旋的操作使二叉树保持平衡

  • 最坏的时间复杂度为O(log2n)

二叉平衡树.png二叉平衡树1.png

二、二叉树的遍历以及实现方式

根据根节点的遍历顺序分为前序、中序、后序,另外还有按层次从上到下从左到右的层序遍历。以下是代码实现:

public class BNode { //节点类
    public Object element;  //节点元素的值
    public BNode lChild;    //左子节点
    public BNode rChild;    //又子节点

    public BNode(Object element, BNode lChild, BNode rChild) {
        this.element = element;
        this.lChild = lChild;
        this.rChild = rChild;
    }

    public BNode(Object element){
        this.element = element;
    }
}
  • 前序遍历:根节点->左子树->右子树
  • 中序遍历:左子树->根节点->右子树
  • 后序遍历:左子树->右子树->根节点
  • 层序遍历:从根节点开始,从上到下,从左到右。
public class BinaryTree {
    public static BNode root; //根节点
    public static ArrayList<BNode> list = new ArrayList<BNode>(); //用来存放便利的元素
    public static LinkedList<BNode> queue = new LinkedList<>(); //用来进行层序遍历的队列

    /**
     * 对该二叉树进行前序遍历 结果存储到list中 前序遍历
     */
    public static void preOrder(BNode node) {
        list.add(node); // 先将根节点存入list
        // 如果左子树不为空继续往左找,在递归调用方法的时候一直会将子树的根存入list,这就做到了先遍历根节点
        if (node.lChild != null) {
            preOrder(node.lChild);
        }
        // 无论走到哪一层,只要当前节点左子树为空,那么就可以在右子树上遍历,保证了根左右的遍历顺序
        if (node.rChild != null) {
            preOrder(node.rChild);
        }
    }

    //中序遍历
    public static void inOrder(BNode node) {
        if (node.lChild != null) {
            inOrder(node.lChild);
        }

        list.add(node);

        if (node.rChild != null) {
            inOrder(node.rChild);
        }
    }
    //后序遍历
    public static void postOrder(BNode node) {

        if (node.lChild != null) {
            postOrder(node.lChild);
        }

        if (node.rChild != null) {
            postOrder(node.rChild);
        }

        list.add(node);
    }
    //层序遍历
    public static void levelOrder(BNode node){
        if (node == null){
            return; 
        }
        BNode currNode = null; //定义一个当前节点,指向出队的节点
        queue.offer(node); //根节点入队
        while (!queue.isEmpty()){ //队列非空则循环
            currNode = queue.poll();  //当前节点,也就是刚出队的节点
            System.out.print(currNode.element.toString());
            if (currNode.lChild!=null){  //判断是否有左子节点
                queue.offer(currNode.lChild);  //左子节点存在则入队
            }
            if (currNode.rChild!=null){  //判断是否有右子节点
                queue.offer(currNode.rChild);  //右子节点存在则入队
            }
        }
    }

    public static void init(){ //初始化一颗二叉树,并赋值给root
        BNode A = new BNode("A");
        BNode B = new BNode("B");
        BNode C = new BNode("C");
        BNode D = new BNode("D");
        BNode E = new BNode("E");
        BNode F = new BNode("F");
        BNode G = new BNode("G");
        BNode H = new BNode("H");

        A.lChild = B;
        B.lChild = D;
        B.rChild = E;
        E.rChild = F;
        A.rChild = C;
        C.rChild = G;
        C.lChild = H;

        root = A;
    }
    public static void main(String[] args) {

        init();
        //preOrder(root); //ABDEFCHG
        //inOrder(root);  //DBEFAHCG
        //postOrder(root);  //DFEBHGCA
        levelOrder(root); //ABCDEHGF
        for (BNode node:list) {
            System.out.print(node.element.toString());
        }
    }
}

前序、中序、后序遍历的方式都是采用递归的方式进行的,很好理解。层序遍历借用了一个队列进行,依次入队父节点(根节点)、左子节点、右子节点,队列为空则遍历完成。