二叉树的概念

树有很多种,而二叉树就是每个节点的度最多为2的树。说通俗一点,就是每个节点最多最多只能有两个子节点的树。

已知两个节点 java 两个节点的树_完全二叉树



已知两个节点 java 两个节点的树_完全二叉树_02


图2:图中这棵树就不是二叉树,因为有节点的度为3,大于了2

二叉树的特点
  1. 每个节点最多有两棵子树,所以二叉树中不存在度大于2的节点。
  2. 子节点有左右之分,左子节点衍生的树是左子树,右子节点衍生的树叫右子树。
  3. 左子树和右子树是有顺序的,次序不能任意颠倒。
  4. 即使树中某个节点只有一颗子树,也要区分是左子树还是右子树。

已知两个节点 java 两个节点的树_二叉树_03


图3:如果只从形态上考虑,三个节点形成的树只有两种情况,树1和其他四种中的一种,但是对于二叉树来说,由于需要区分左右,所以就演变成了5种形态,所以树2~5代表不同的二叉树。

二叉树具有5种基本形态
  1. 空二叉树
  2. 只有一个根节点
  3. 根节点只有左子树
  4. 根节点只有右子树
  5. 根节点只有左子树又有右子树
特殊二叉树
1.斜树

顾名思义,斜树就是斜的。所有的节点只有左子树的二叉树叫做左斜树,所有的节点只有右子树的二叉树叫做右子树,二者统称为斜树。

2.满二叉树

满的意思就是完美,如果这颗二叉树的所有节点都存在左子树和右子树,且所有的叶子节点都在同一层,那么这很完美,这样的二叉树被称为满二叉树。如图4就是一颗满二叉树。

已知两个节点 java 两个节点的树_完全二叉树_04


图4:满二叉树

满二叉树有以下特点:

  • 叶子节点只能出现在最后一层。出现在其他层就不可能达到平衡。
  • 非叶子节点的度一定为2,否则就缺胳膊少腿了。
  • 在同样深度的二叉树中,满二叉树的节点最多,叶子最多。
3.完全二叉树

对一颗具有n个节点的二叉树按层序编号,如果编号为i(1<=i<=n)d的节点与同样深度的满二叉树中编号为i的节点在二叉树中位置完全相同,则这棵树被称为满二叉树。如图5就是一颗完全二叉树,图6和图7都不是完全二叉树。

已知两个节点 java 两个节点的树_子树_05


图5:完全二叉树

已知两个节点 java 两个节点的树_已知两个节点 java_06


图6:序号为5的节点缺失了左子树,因此不是完全二叉树

已知两个节点 java 两个节点的树_二叉树_07


图7:序号为3的节点缺失了子节点,也不是完全二叉树



小窍门:从上至下、从左到右,按顺序给节点编号,如果编号出现空档,这就不是完全二叉树。

完全二叉树的特点:

  • 叶子节点只能出现在最后两层。
  • 最下层的叶子节点一定集中在左部连续位置。
  • 倒数第二层的叶子节点一定集中在右部连续位置。
  • 若存在度为1的节点,则该节点一定只有左孩子。
  • 同样节点数的二叉树,完全二叉树的深度最小。
二叉树的性质
性质1:在二叉树的第i层,最多有2i-1个节点(i>=1)
性质2:深度为k的二叉树最多有2k-1个节点(k>=1)
性质3:对于任意一颗二叉树T,如果叶子节点的数量为n0,度为1的节点的数量为n1,度为2的节点的数量为n2,节点总数为n,那么n=n0+n1+n2,n0 = n2+1
性质4:具有n个节点的完全二叉树的深度为log2n+1,其中log2n向下取整。
性质5:如果对一颗有n个节点为完全二叉树的节点从上到下、从左到右编号,对任一节点i右:
  • 如果i=1,则i是二叉树的根,无双亲;如果i>1,则其双亲是i/2(向下取整)。
  • 如果2i>n,则节点i无左孩子,节点i为叶子节点,否则其做孩子是节点2i。
  • 如果2i+1>n,则节点i无右孩子,否则其有孩子是节点2i+1。

已知两个节点 java 两个节点的树_二叉树_08


图8:可以结合此图来理解性质5



二叉树的遍历

二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中的所有节点,使得每个节点被访问且只被访问一次。

前序遍历(根左右)

规则:若二叉树为空,则返回空操作;否则访问根节点,然后前序遍历左子树,再前序遍历右子树。

如图9,前序遍历的结果是ABDGHCEIF

已知两个节点 java 两个节点的树_完全二叉树_09


图9:前序遍历 ABDGHCEIF


中序遍历(左根右)

规则:若二叉树为空,则返回空操作;否则从跟接单开始(注意并不是先访问根节点),中序遍历左子树,然后访问根节点,最后中序遍历右子树。
如图10,中序遍历的结果是GDHBAEICF

已知两个节点 java 两个节点的树_子树_10


图10:中序遍历 GDHBAEICF



后序遍历(左右根)

规则:若二叉树为空,则返回空操作;否则从根节点开始(注意也不是先访问根节点),后序遍历左子树,然后后序遍历右子树,最后访问根节点。

如图11,后序遍历的结果是GHDBIEFCA

已知两个节点 java 两个节点的树_二叉树_11


图11:后序遍历 GHDBIEFCA


二叉树的存储结构

顺序存储结构

顺序存储结构就是用一维数组存储二叉树中的节点,节点在数组中的下标要能体现出节点之间的逻辑关系,比如双亲与孩子的关系。

已知两个节点 java 两个节点的树_二叉树_12


图12:由于完全二叉树的定义严格,用顺序结构也可以表现出二叉树的结构来


已知两个节点 java 两个节点的树_已知两个节点 java_13


图13


对于一般的二叉树。尽管层序编号不能反映逻辑关系,但是可以将其按完全二叉树编号,只不过把不存在的节点设置为“^”,如图14所示。

已知两个节点 java 两个节点的树_完全二叉树_14


图14


考虑一种极端情况,一棵深度为k的右斜树,他只有k个节点,却要分配2k-1个节点,这明显就是浪费空间,如图15所示,所以顺序存储的结构一般只用于完全二叉树,堆排序就用到了完全二叉树的顺序存储。

已知两个节点 java 两个节点的树_完全二叉树_15


图15


二叉链表
既然顺序存储结构适用性不强,我们就要考虑链式存储结构,二叉树每个节点最多有两个孩子,所以为他设计一个数据域和两个指针域是比较自然的想法,像这种链表叫做二叉链表。链表的节点如图16所示,图17是一颗用二叉链表组织的二叉树。

已知两个节点 java 两个节点的树_二叉树_16


图16:二叉链表节点图,其中data是数据域,lchild、rchild都是指针域,分别存放指向左孩子和右孩子的指针。

已知两个节点 java 两个节点的树_完全二叉树_17


图17


代码实现

/**
 * 定义二叉树,使用二叉链表存储结构
 *
 * @author chenzhiyuan
 * @date 2019-10-02 22:52
 */
public class BiTree {
    private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    // 二叉树的根节点
    private BiTNode root = null;

    public static void main(String[] args) throws IOException {
        BiTree tree = new BiTree();
        tree.buildBiTree();
        System.out.println("前序遍历二叉树:");
        tree.preOrderTraverse(tree.root);
        System.out.println("中序遍历二叉树:");
        tree.inOrderTraverse(tree.root);
        System.out.println("后序遍历二叉树:");
        tree.postOrderTraverse(tree.root);

    }

    public void buildBiTree() throws IOException {
        root = buildBiTree(root);
    }

    /**
     * 递归构建二叉树,先输入根节点,然后递归侯建左子树,最后递归构建右子树。
     * 如果输入为#,标识当前节点为null
     *
     * @param node 被构建的树的根节点,可以为null
     * @return 被构建的树的根节点
     */
    public BiTNode buildBiTree(BiTNode node) throws IOException {
        String str = br.readLine();
        if ("#".equals(str)) {
            return null;
        } else {
            if (node == null) {
                node = new BiTNode();
            }
            node.data = str;
        }

        // 构建左子树
        node.lChild = buildBiTree(node.lChild);
        // 构建右子树
        node.rChild = buildBiTree(node.rChild);
        // 返回当前被构建的树的根节点
        return node;
    }

    /**
     * 前序遍历
     *
     * @param node
     */
    public void preOrderTraverse(BiTNode node) {
        if (node == null) {
            return;
        }
        System.out.println(node.data);
        // 遍历左子树
        preOrderTraverse(node.lChild);
        // 遍历右子树
        preOrderTraverse(node.rChild);
    }

    /**
     * 中序遍历
     *
     * @param node
     */
    public void inOrderTraverse(BiTNode node) {
        if (node == null) {
            return;
        }
        inOrderTraverse(node.lChild);
        System.out.println(node.data);
        inOrderTraverse(node.rChild);
    }

    /**
     * 后序遍历
     *
     * @param node
     */
    public void postOrderTraverse(BiTNode node) {
        if (node == null) {
            return;
        }
        postOrderTraverse(node.lChild);
        postOrderTraverse(node.rChild);
        System.out.println(node.data);
    }

    /**
     * 二叉链表的节点定义
     */
    private class BiTNode {
        private BiTNode lChild;
        private String data;
        private BiTNode rChild;

        @Override
        public String toString() {
            return "BiTNode{" +
                    "data='" + data + '\'' +
                    '}';
        }
    }
}

测试:使用如12所示的二叉树

A
B
D
H
#
#
I
#
#
E
J
#
#
#
C
F
#
#
G
#
#
前序遍历二叉树:
A
B
D
H
I
E
J
C
F
G
中序遍历二叉树:
H
D
I
B
J
E
A
F
C
G
后序遍历二叉树:
H
I
D
J
E
B
F
G
C
A