二叉树

1 基础介绍

1.1 树的基本概念

树的定义、根结点、树的结点、结点的路径、路径的长度、结点的度、树的度、结点点、分支结点、子结点、父结点、祖先结点、子孙结点、兄弟结点、树的深度、有序树/无序树、森林。

1.2 二叉树介绍与性质

二叉树每个结点最多两个子结点,且有左右之分,是有序树。特殊的二叉树有满二叉树,完全二叉树,左支树/右支树。重要的是记住二叉树的性质,这在编程中可能用到:
性质1 二叉树中第 i ( i ≥ 0 ) i(i \geq 0) i(i0)层上的结点数最多为 2 i 2^i 2i;
性质2 深度为 h ( h ≥ 1 ) h(h \geq 1) h(h1)的二叉树中最多有 2 h − 1 2^h-1 2h1个结点;
性质3 对于任何一颗二叉树,若其叶节点的个数为 n 0 n_0 n0,度为2的结点个数为 n 2 n_2 n2,则有 n 0 = n 2 + 1 ⟸ n = n 0 + n 1 + n 2 , e = n 1 + 2 × n 2 , n = e + 1 n_0 = n_2 + 1 \Longleftarrow n = n_0 + n_1 + n_2, e = n_1 + 2\times n_2, n = e + 1 n0=n2+1n=n0+n1+n2,e=n1+2×n2,n=e+1;
性质4 具有n个结点的完全二叉树,其深度为 ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n \rfloor + 1 log2n+1 ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_2(n+1) \rceil log2(n+1)
性质5 对于具有n个结点的完全二叉树,若从根节点开始自上到下并且按照层次由左向右对结点从0开始进行编号,则对于任意一个编号为 i ( i ≤ n ) i(i\leq n) i(in)的结点有:

  1. i = 0 i=0 i=0,则编号为i的结点是二叉树的根节点;若 i > 1 i>1 i>1,则编号为i的结点其父节点的编号为 ⌊ i − 1 2 ⌋ \lfloor \frac{i-1}{2} \rfloor 2i1;
  2. 2 i + 1 ≥ n 2i+1\geq n 2i+1n,则编号为 i i i的结点无左孩子,否则编号为 2 i + 1 2i+1 2i+1的结点就是其左孩子。
  3. 2 i + 2 ≥ n 2i+2\geq n 2i+2n,则编号为 i i i的结点无右孩子,否则编号为 2 i + 2 2i+2 2i+2的结点就是其右孩子。

1.3 二叉树的存储

二叉树存储方式有顺序存储和链式存储。完全二叉树和满二叉树适合顺序存储,其余则适合链式存储。链式存储又有二叉链表和三叉链表两种。

2 二叉树的遍历

代码中的BiTreeNode、LinkStack、LinkQueue为自己实现,不是java包中的类。
二叉树的访问有7种方式,分别为:层次遍历、DLR、LDR、LRD、DRL、RDL和RLD, 其中(L表示lChild,D表示Data,R表示rChild)。由于2-4和5-7只有左右之分,其遍历方式无本质区别,所以我们只讨论前四种遍历方式。

2.1 先根遍历(DLR)

递归方法:若二叉树为空则为空操作,否则

  1. 访问根节点;
  2. 先根遍历左子树;
  3. 先根遍历右子树。
public void preRootTraverse(BiTreeNode T) {
    if (T != null) {
        System.out.print(T.getData()); // 访问根结点
        preRootTraverse(T.getLchild());// 访问左子树
        preRootTraverse(T.getRchild());// 访问右子树
    }
}

非递归方法:

  1. 创建一个栈对象,根节点入栈;
  2. 若栈非空,将栈顶结点弹出栈并访问该节点;
  3. 对当前访问结点的非空左孩子结点相继依次访问,并将当前访问结点的非空右孩子结点压入栈内;
  4. 重复执行步骤2.和3.,直到栈为空为止。
public void preRootTraverse() {
    BiTreeNode T = root;
    if (T != null) {
        LinkStack S = new LinkStack();// 构造栈
        S.push(T);// 根结点入栈
        while (!S.isEmpty()) {
            T = (BiTreeNode) S.pop();// 移除栈顶结点,并返回去值
            System.out.print(T.getData()); // 访问结点
            while (T != null) {
                if (T.getLchild() != null) // 访问左孩子
                    System.out.print(T.getLchild().getData()); // 访问结点

                if (T.getRchild() != null)// 右孩子非空入栈
                    S.push(T.getRchild());

                T = T.getLchild();
            }
        }
    }
}

2.2 中根遍历(LDR)

递归方法:若二叉树为空则为空操作,否则

  1. 中根遍历左子树;
  2. 访问根节点;
  3. 中根遍历右子树。
public void inRootTraverse(BiTreeNode T) {
    if (T != null) {
        inRootTraverse(T.getLchild());// 访问左子树
        System.out.print(T.getData()); // 访问根结点
        inRootTraverse(T.getRchild());// 访问右子树
    }
}
	

非递归方法:

  1. 创建一个栈对象,根节点入栈;
  2. 当栈为非空时,将栈顶结点的非空左孩子相继入栈;
  3. 栈顶结点出栈并访问非空栈顶结点,并使该栈顶结点的非空右孩子结点入栈;
  4. 重复执行步骤2.和3.,直到栈为空为止。
public void inRootTraverse() {
    BiTreeNode T = root;
    if (T != null) {
        LinkStack S = new LinkStack();// 构造链栈
        S.push(T); // 根结点入栈
        while (!S.isEmpty()) {
            while (S.peek() != null)
                // 将栈顶结点的所有左孩子结点入栈
                S.push(((BiTreeNode) S.peek()).getLchild());
            S.pop(); // 空结点退栈
            if (!S.isEmpty()) {
                T = (BiTreeNode) S.pop();// 移除栈顶结点,并返回去值
                System.out.print(T.getData()); // 访问结点
                S.push(T.getRchild());// 结点的右孩子入栈
            }
        }
    }
}

2.3 后根遍历(LRD)

递归方法:若二叉树为空则为空操作,否则

  1. 后根遍历左子树;
  2. 后根遍历右子树;
  3. 访问根节点。
public void postRootTraverse(BiTreeNode T) {
    if (T != null) {
        postRootTraverse(T.getLchild());// 访问左子树
        postRootTraverse(T.getRchild());// 访问右子树
        System.out.print(T.getData()); // 访问根结点
    }
}

非递归方法:

  1. 创建一个栈对象,根节点入栈, p赋初始值null;
  2. 若栈非空,将栈顶结点的非空左孩子相继入栈;
  3. 若栈非空,查看栈顶结点,若栈顶结点的右孩子为空,或者与p相等,则将栈顶结点弹出栈并访问它,同时使p指向该节点,并置flag值为true;否则,将栈顶结点的右孩子入栈,并置flag为false;
  4. 若flag值为true, 则重复执行步骤3. , 否则,重复执行步骤2.和3.,直到栈为空为止。
public void postRootTraverse() {
    BiTreeNode T = root;
    if (T != null) {
        LinkStack S = new LinkStack();// 构造链栈
        S.push(T); // 根结点进栈
        Boolean flag;// 访问标记
        BiTreeNode p = null;// p指向刚被访问的结点
        while (!S.isEmpty()) {
            while (S.peek() != null)
                // 将栈顶结点的所有左孩子结点入栈
                S.push(((BiTreeNode) S.peek()).getLchild());
            S.pop(); // 空结点退栈
            while (!S.isEmpty()) {
                T = (BiTreeNode) S.peek();// 查看栈顶元素
                if (T.getRchild() == null || T.getRchild() == p) {
                    System.out.print(T.getData()); // 访问结点
                    S.pop();// 移除栈顶元素
                    p = T;// p指向刚被访问的结点
                    flag = true;// 设置访问标记
                } else {
                    S.push(T.getRchild());// 右孩子结点入栈
                    flag = false;// 设置未被访问标记
                }
                if (!flag)
                    break;
            }
        }
    }
}

2.4 层次遍历

  1. 创建一个队列对象,根节点入队;
  2. 若队列非空,则将队首结点出队并访问该节点,再将该节点的非空左、右孩子结点依次入队;
  3. 重复执行步骤2. ,直到队列 为空为止。
public void levelTraverse() {
    BiTreeNode T = root;
    if (T != null) {
        LinkQueue L = new LinkQueue();// 构造队列
        L.offer(T);// 根结点入队列
        while (!L.isEmpty()) {
            T = (BiTreeNode) L.poll();
            System.out.print(T.getData()); // 访问结点
            if (T.getLchild() != null)// 左孩子非空,入队列
                L.offer(T.getLchild());
            if (T.getRchild() != null)// 右孩子非空,入队列
                L.offer(T.getRchild());
        }
    }
}

树的应用

  1. 查询每个结点的值
  2. 求左叶子节点的和
  3. 求二叉树直径
  4. 判断树是否对称
  5. 判断两颗树是否相等
  6. 镜像翻转一颗树
  7. N叉树的层次遍历和中序遍历
  8. 后序遍历
  9. 求和位某个数的路径条数
  10. 二叉树最大深度和最小深度
  11. 求N树的最大深度
  12. 二叉树的坡度
  13. t是否是s的子树
  14. 合并两颗二叉树
    ……
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值