数据结构之树与二叉树概念

1. 树

1.1 树的概念

在这里插入图片描述

  1. 相同父节点的节点称为兄弟节点;
  2. 最上方的节点称为根节点;
  3. 没有任何节点的树称为空树;
  4. 子树:节点的子节点也可以称为子树;
  5. 节点的度:子树的个数;
  6. 树的度:所有节点度中的最大值;
  7. 叶子节点:度为0的节点;
  8. 非叶子节点:度不为0的节点;
  9. 层数:根节点为第一层,往下累加;
  10. 节点的深度:根节点到该节点的唯一路径上的节点总数;
  11. 节点的高度:从当前节点到最远的叶子节点的路径上的节点总数;
  12. 树的深度:所有节点深度的最大值;
  13. 树的高度:所有节点高度的最大值;
  14. 的深度等于树的高度。
  15. 有序树:树中任意节点的子节点之间有顺序关系;
  16. 无序树:树种任意节点的子节点之间没有顺序关系。
  17. 森林:由多个不相交的树组成的集合。

2. 二叉树(Binary Tree)

在这里插入图片描述

  1. 每个节点的度最大为2;
  2. 左子树和右子树是由顺序的;
  3. 即使只有一颗子树也要区分左右树

2.1 二叉树性质

  1. 非空二叉树的第 i 层,最多由 2 ^ (i -1) 个节点 ( i >= 1);
  2. 高度为 h 的二叉树上最多有 2 ^ h - 1 个节点 (h >= 1);
  3. 对于任何一颗非空二叉树,如果叶子节点的个数为 n0 ,度为2的节点个数为n2,则n0 = n2 +1;
    推导过程:①设度为1的节点个数为n1,则二叉树全部节点个数n = n0 + n1 + n2;②二叉树的全部边T = n1 + 2 *n2 = n -1 = n0 + n1 + n2 - 1;如下图:边的数量等于各个节点度的和 = n1 + 2 * n2;又等于每个节点上方的线 = n - 1 并且 n = n0 + n1 + n2 ,可以得出n0 = n2 + 1。

在这里插入图片描述

2.2 真二叉树(Proper Binary Tree)

所有节点的度要么为0,要么为2。

在这里插入图片描述

2.3 满二叉树(Full Binary Tree)

所有节点的度要么为0,要么为2。且所有的叶子节点都在最后一层。

在这里插入图片描述

  1. 在同样高度的二叉树中,满二叉树的叶子节点数量最多,总结点数量也最多。
  2. 满二叉树一定是真二叉树,真二叉树不一定是满二叉树。

2.4 完全二叉树(Complete Binary Tree)

叶子节点只会出现在最后两层,且最后一层的叶子节点都靠左对齐。也就是说如果某个节点只有一个叶子节点一定是左边的。
从根节点到倒数第二层是一颗满二叉树。

在这里插入图片描述
完全二叉树性质:

  1. 度为1的节点只有左子树;
  2. 度为1的节点要么是1个,要么是0个;
  3. 同样节点数量的二叉树,完全二叉树的高度最小;
  4. 高度为h的完全二叉树:最少2 ^ (h - 1) 个节点;最多2 ^ h - 1 个节点;所以总节点数量: 2 ^ (h - 1) ≤ n < 2 ^ h - 1 => h -1 ≤ log2n < h ;可以得出高度h = floor( log2n ) + 1;注:①floor:向下取整;②ceiling:向上取整。
  5. 一颗有n 个节点的完全二叉树,从上到下、从左到右对节点从1开始进行编号,对任意第i个节点:
    ①i = 1, 为根节点;
    ②i > 1,它的父节点编号为floor( i / 2)
    ③2i ≤ n ,它的左子节点编号为2i;
    ④2i > n,它无左子节点;
    ⑤2i + 1≤ n ,它的右子节点编号为 2i + 1;
    ⑥2i + 1 > n ,无右子节点。
  6. 叶子节点数量:n0 = floor( ( n + 1 ) / 2 ) = ceiling( n / 2)
  7. 非叶子节点数量:n1 + n2 = floor( n / 2 ) = ceiling( ( n - 1 ) / 2)

测试题:一个完全二叉树有768个节点,求叶子节点数量:
解: 设度为0的节点为n0,度为1的节点为n1,度为2的节点为n2;可以得出 n = n0 + n1 + n2;又因为 n0 = n2 + 1;可以得出:n = 2n0 + n1 - 1;因为完全二叉树中只有1个或者0个度为1的节点,所以 n0 = [floor ( 768 - 1 ) + 1 ] / 2 = 384

2.5 二叉树遍历

  • 节点和成员变量设计:
	// 节点数量
    private int size;

    // 根节点
    private Node<E> root;

    // 节点类
    private static class Node<E>{
        // 元素值
        E e;
        // 左子节点
        Node<E> left;
        // 右子节点
        Node<E> right;
        // 父节点
        Node<E> parent;
        public Node(E e, Node<E> parent){
            this.e = e;
            this.parent = parent;
        }
    }
2.5.1 前序遍历(Preorder Traversal)

顺序:根节点 → 前序遍历左子树 → 前序遍历右子树。

在这里插入图片描述

  • 递归实现前序遍历
	/**
     * 前序遍历
     */
    public void preorderTraversal(){
        preorderTraversal(root);
    }

    private void preorderTraversal(Node<E> node){
        if (node == null){
            return;
        }
        System.out.println(node.e);
        preorderTraversal(node.left);
        preorderTraversal(node.right);
    }
2.5.2 中序遍历(Inorder Traversal)

顺序:中序遍历左子树 → 根节点 → 中序遍历右子树。

在这里插入图片描述

  • 递归实现中序遍历:
	/**
     * 中序遍历
     */
    public void inorderTraversal(){
        inorderTraversal(root);
    }

    private void inorderTraversal(Node<E> node){
        if (node == null){
            return;
        }
        inorderTraversal(node.left);
        System.out.println(node.e);
        inorderTraversal(node.right);
    }
2.5.2 后序遍历(Postorder Traversal)

顺序:后序遍历左子树 → 后序遍历右子树 → 根节点。

在这里插入图片描述

  • 递归实现后序遍历:
	/**
     * 后序遍历
     */
    public void postorderTraversal(){
        postorderTraversal(root);
    }

    private void postorderTraversal(Node<E> node){
        if (node == null){
            return;
        }
        postorderTraversal(node.left);
        postorderTraversal(node.right);
        System.out.println(node.e);
    }
2.5.2 层序遍历(Level OrderTraversal)

从上到下、从左到右依次访问每一个节点。

在这里插入图片描述

  • 使用队列实现层序遍历:从根节点开始加入到队列中,弹出根节点,如果左子节点不为空就加入左子节点,右子节点不为空就加入右子节点。
	/**
     * 层序遍历
     */
    public void levelOrderTranversal(){
        if (root == null) {
            return;
        }
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            Node<E> node = queue.poll();
            System.out.println(node.e);
            if (node.left != null){
                queue.offer(node.left);
            }
            if (node.right != null){
                queue.offer(node.right);
            }
        }
    }

2.6 获取树的高度

2.6.1 递归实现
	/**
     * 获取二叉树高度
     * @return 高度
     */
    public int height(){
        return height(root);
    }

    private int height(Node<E> node){
        if (node == null){
            return 0;
        }
        // 根节点的高度等于它的左子节和右子节点之间的高度的最大值加一
        return 1 + Math.max(height(node.left),height(node.right));
    }
2.6.2 迭代实现

poll()出一个元素之后levelSize减一,如果levelSize的值为0时就表示当前层已经执行完毕,进入下一层,当前层数的所有节点已经offer()到队列中,因此levelSize的值就是队列的元素数量。

/**
     * 迭代获取高度
     * @return
     */
    public int heightIterator(){
        if (root == null) {
            return 0;
        }
        int height = 0;
        // 记录每一层元素的数量
        int levelSize = 1;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            Node<E> node = queue.poll();
            levelSize--;

            if (node.left != null){
                queue.offer(node.left);
            }
            if (node.right != null){
                queue.offer(node.right);
            }
            // 等于0表示当前层访问完毕。
            if (levelSize == 0){
                levelSize = queue.size();
                height++;
            }
        }
        return height;
    }

2.7 判断是否为完全二叉树

一个二叉树的子节点一共有四种情况出现:

  1. 左右子节点都不为空:正常情况,将它的左右子节点入队;
  2. 左子节点为空,右不为空:不是完全二叉树;
  3. 左子节点不为空,右子节点为空:它后面的节点必须都是叶子节点才符合,
  4. 左右都为空:后面也都是叶子节点才符合。
	/**
     * 判断树是否为完全二叉树
     * @return
     */
    public boolean isCompletePro(){
        if (root == null){
            return false;
        }
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);

        boolean leaf = false;
        while (!queue.isEmpty()){
            Node<E> node = queue.poll();

            if (leaf && !node.isLeaf()){
                return false;
            }

            if (node.left != null){
                queue.offer(node.left);
            }else if(node.right != null){
                return false;
            }

            if (node.right != null){
                queue.offer(node.right);
            }else {
                // 只要出现右侧为空的情况,后面遍历的所有节点都需要是叶子节点
                leaf = true;
            }

        }
        return true;
    }

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
https://leetcode-cn.com/problems/invert-binary-tree/

  • 前序遍历递归法实现:
    /**
     * 前序遍历实现翻转
     * @param root
     * @return
     */
    public TreeNode invertTree(TreeNode root) {
        if (root == null){
            return null;
        }

        // 交换节点
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;

        invertTree(root.left);
        invertTree(root.right);

        return root;
    }
  • 层序遍历迭代法实现:
	/**
     * 层级遍历实现翻转
     * @param root
     * @return
     */
    public TreeNode invertTreeLevel(TreeNode root){
        if (root == null) {
            return root;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            TreeNode node = queue.poll();

            // 交换节点
            TreeNode tmp = node.left;
            node.left = node.right;
            node.right = tmp;

            if (node.left != null){
                queue.offer(node.left);
            }
            if (node.right != null){
                queue.offer(node.right);
            }
        }
        return root;
    }

2.8 重构二叉树

能够确定唯一二叉树:

  1. 前序遍历 + 中序遍历;
  2. 后序遍历 + 中序遍历;
  3. 前序遍历 + 后序遍历,并且是一颗真二叉树

2.9 前驱节点

中序遍历时的前一个节点称为前驱节点。

  1. 如果当前节点的左子节点不为空node.left != null:那么它的前驱节点一定是它的左子树的最后一个right节点predecessor = node.left.right.right...直到right为null时退出。
    在这里插入图片描述

  2. 如果当前节点的左子节点为空并且它的父节点不为空node.left == null && node.parent != null:那么它的前驱节点一定在它的父节点中predecessor = node.parent.parent...,直到node是父节点的右子树跳出循环。
    在这里插入图片描述

  3. 如果左子节点为空并且父节点为空node.left == null && node.parent == null:它就没有前驱节点。在这里插入图片描述

    /**
     * 获取当前节点的前驱节点
     * @param node
     * @return
     */
    private Node<E> predecessor(Node<E> node){
        if (node == null) {
            return null;
        }
        Node<E> prev = node.left;

        // 前驱在左子树中
        if (prev != null){
            while (prev.right != null){
                prev = prev.right;
            }
            return prev;
        }

        // 前驱在祖父节点中
        while (node.parent != null && node == node.parent.left){
            node = node.parent;
        }
        // 直接返回node.parent即可
        // 1. node.parent == null;
        // 2. node == node.parent.right;
        return node.parent;
    }

2.10 后继节点

后继节点:中序遍历时的后一个节点。

  1. 右子节点不为空node.right != null:即找到右子树不断的往左找,直到左子节点为空successor = node.right.left.left...,left为空退出。
  2. 右子节点为空并且父节点不为空node.right == null && node.parent != null:往父节点的方向找successor = node.parent.parent...,直到该节点为某个父节点的左子树退出node = node.parent.left,那么当前父节点就是它的后继节点。
  3. 右子节点为空并且父节点为空,肯定没有后继节点。
	/**
     * 获取后继节点
     * @param node
     * @return
     */
    private Node<E> successor(Node<E> node){
        if (node == null) {
            return null;
        }
        Node<E> prev = node.right;

        // 前驱在左子树中
        if (prev != null){
            while (prev.left != null){
                prev = prev.left;
            }
            return prev;
        }

        // 前驱在祖父节点中
        while (node.parent != null && node == node.parent.right){
            node = node.parent;
        }
        return node.parent;
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值