数据结构之二叉树

一、树的基础概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看
起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  1. 从根结点出发分成的M个子集,彼此之间不能相交。
  2. 有一个特殊的节点,称为根节点,根节点没有前驱节点

在这里插入图片描述

二、树的常用术语

术语含义
节点的度一个节点含有的子树的个数;如上图:A的为6
树的度一棵树中,最大的节点的度;如上图:树的度为6
叶子节点或终端节点度为0的节点;如上图:B、C、H、I…等节点为叶节点
双亲节点或父节点若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
根结点一棵树中,没有双亲结点的结点;如上图:A
节点的层次从根开始定义起,根为第1层,根的子节点为第2层
树的高度或深度树中节点的最大层次; 如上图:树的高度为4
非终端节点或分支节点度不为0的节点
兄弟节点具有相同父节点的节点互称为兄弟节点
堂兄弟节点双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林由m(m>=0)棵互不相交的树的集合称为森林

三、二叉树(重点)

3.1 二叉树的定义

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉
树组成。
二叉树的特点:

  1. 每个结点最多有两棵子树,即二叉树不存在度大于 2 的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树。

3.2 特殊的二叉树

  1. 满二叉树: 一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果
    一个二叉树的层数为K,且结点总数是2**k** - 1,则它就是满二叉树。
  2. 完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n
    个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全
    二叉树。 要注意的是满二叉树是一种特殊的完全二叉树

在这里插入图片描述

  1. 二分查找树(BST):结点的值之间满足:左子树的节点值 < 根节点值 < 右子树的节点值

在这里插入图片描述

  1. 平衡二叉树:该树中任意一个结点的左右子树高度差<=1

3.3 二叉树的常用性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2i-1(i>0)个结点

  2. 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2k - 1(k>=0)

  3. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1

    推导:由于任意二叉树满足节点个数n和边长x,x = n - 1 的性质

    在二叉树中只有三种节点n0,n1,n2

    n0 + n1 + n2 = n //0个子树的结点 + 1个子树的结点 + 2个子树的结点

    n1 + 2n2 = n-1 //边长的关系,有两个子树的一定有两条边

    最终n0 = n2 + 1

  4. 具有n个结点的完全二叉树的深度k为 log ⁡ 2 ( n − 1 ) \log_2({n - 1}) log2n1 上取整

  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:

    • 若i>0,双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

    • 若2i+1<n,左孩子序号:2i+1,否则无左孩子

    • 若2i+2<n,右孩子序号:2i+2,否则无右孩子

3.4 二叉树的遍历

遍历:按照一定的顺序访问这个集合的所有元素,做到不重复,不遗漏。

3.4.1 深度优先遍历

先序遍历

可以理解为 根 左 右

  1. 先访问根节点

  2. 递归访问左子树

  3. 递归访问右子树

利用栈这个结构演示先序遍历,后面的遍历都是差不多的

根左右

  1. A入栈第一次访问并打印
  2. B入栈第一次访问并打印
  3. D入栈第一次访问并打印
  4. D没有左子树和右子树,访问了三次,已访问完毕,弹出。回到B
  5. 访问B的左子树,将E入栈第一次访问并打印
  6. E没有左子树和右子树,访问了三次,已访问完毕,弹出。回到B
  7. B第三次访问完毕,弹出,回到A
  8. 访问A的左子树,D入栈第一次访问并打印
  9. C访问完毕,弹出返回A
  10. A弹出
  11. 最终:ABDEC

在这里插入图片描述

中序遍历

可以理解为 左 根 右 利用栈就是第一次访问压入栈,第二次访问的时候打印,第三次访问完毕的时候弹出

  1. 递归访问左子树
  2. 访问根节点
  3. 递归访问右子树

后序遍历

可以理解为 左 根 右 利用栈就是第一次访问压入栈,第三次访问的时候打印并弹出

  1. 递归访问左子树

  2. 递归访问右子树

  3. 最后访问根节点

3.4.2 广度优先遍历

层序遍历

按照编号顺序依次访问,可以借助队列来实现

借助队列实现的步骤

  1. A入队
  2. A的左右子树不为空,BC入队,A出队
  3. B的左右子树不为空,DE入队,B出队
  4. C的右子树不为空,F入队,C出队
  5. D的左右子树为空,D出队
  6. E的左子树不为空,G入队,E出队
  7. F的左右子树为空,F出队
  8. G的右子树不为空,H入队,G出队
  9. H的左右子树为空,H出队

特点:

每当遍历一层结点结束,队列中存储了下一层要遍历的结点;当整个队列为空,二叉树的层序遍历就结束了。

在这里插入图片描述

3.4.3 举例

在这里插入图片描述

特点:

  1. 前序遍历的第一个节点一定是根节点
  2. 中序遍历左子树的在根节点的左侧,左子树在根节点的右侧
  3. 后序遍历的结果集–转置reverse(ACFBEGHD)–根右左

3.5 二叉树遍历的实现

//二叉树的结点
class Node<E>{
    E val;
    Node<E> left;
    Node<E> right;
    public Node(E val){
        this.val = val;
    }
}

public class BinaryTree_<E> {
    Node<Character> root;
    //建立一颗测试二叉树
    public void build(){
        Node<Character> node = new Node<>('A');
        Node<Character> node1 = new Node<>('B');
        Node<Character> node2 = new Node<>('C');
        Node<Character> node3 = new Node<>('D');
        Node<Character> node4 = new Node<>('E');
        Node<Character> node5 = new Node<>('F');
        Node<Character> node6 = new Node<>('G');
        Node<Character> node7 = new Node<>('H');
        node.left = node1;
        node.right = node2;
        node1.left = node3;
        node1.right = node4;
        node4.left = node6;
        node6.right = node7;
        node2.right = node5;
        root = node;
    }
    
    /**
     * 先序遍历
     * @param root
     */
    public  void preorderTraversal(Node root){
        if (root == null){
            return;
        }
        //先打印根节点
        System.out.println(root.val);
        //递归访问左子树
        preorderTraversal(root.left);
       // 递归访问右子树
        preorderTraversal(root.right);
    }

    /**
     * 中序遍历
     * @param root
     */
    public void inorderTraversal(Node root){
        if (root == null){
            return;
        }
        preorderTraversal(root.left);
        System.out.println(root.val);
        preorderTraversal(root.right);
    }

    /**
     * 后序遍历
     * @param root
     */
    public void postorderTraversal(Node root){
        if (root == null){
            return;
        }
        preorderTraversal(root.left);
        preorderTraversal(root.right);
        System.out.println(root.val);;
    }

    /**
     * 求节点个数
     * @param root
     * @return
     */
    //理解语义:这个函数是帮我们求节点的个数,我们只能知道根节点,下面的结点不知道,那就让子函数帮我们完成。
    //最终就是左子树的结点个数子函数完成,左子树的节点个数子函数完成,最后加上根节点就搞定了
    public int nodeCount(Node root){
        if (root == null){
            return 0;
        }
        //左子树结点 + 右子树结点 + 根节点
        return 1 + nodeCount(root.left) + nodeCount(root.right);
    }

    /**
     * 求叶子节点的个数
     * @param root
     * @return
     */
    //理解语义:这个函数是帮我们求叶子节点的个数。
    //当这棵树是有左子树和右子树的时候,我们只知道根节点,那么求左子树,右子树叶子节点交给子函数,最后只要把左子树叶子结点 + 右子树叶子节点就可以
    public int leafNode(Node root){
        //数为空,叶子节点为0
        if (root == null){
            return 0;
        }
        //只有一个节点即根节点,叶子节点为1
        if (root.left == null && root.right == null){
            return 1;
        }
        return leafNode(root.left) + leafNode(root.right);
    }

    /**
     *第k层节点的个数
     * @param root
     * @param k
     * @return
     */
    //理解语义:这个函数是求第k层结点的个数
    //当树有左子树和右子树的时候,我们只知道根节点。假设我们求得是第三层的节点个数。可以拆封成求左右子树第二层的节点个数 + 求左右子树的左右子树的第一层结点,就形成了递归
    public int nodeByK(Node root, int k){
        //当数为空或者k < 0,结点个数为0
        if (root == null || k < 0){
            return 0;
        }
        //k是第一层,只有一个结点的时候
        if (k == 1){
            return 1;
        }
        return nodeByK(root.left,k - 1) + nodeByK(root.right, k - 1);
    }

    /**
     * 查找节点
     * @param root
     * @return
     */
    //这个比较简单,现在左子树递归找符合条件的结点,找到了直接返回。没找到在递归去访问右子树找符合条件的结点,找到了返回。
    public boolean find(Node root, int val){
        //树为空
        if (root == null){
            return false;
        }
        //根节点就是要找的结点
        if (root.val == val){
            return true;
        }
        return find(root.left,val) || find(root.right,val);
    }
    /**
     * 借助队列,实现二叉树的层序遍历
     * @param root
     */
    public void levelOrder(Node<E> root) {
        //双端队列,存储的类型是Node
        Deque<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        // 循环的终止条件就是队列为空
        while (!queue.isEmpty()) {
            // 取出当前层的节点个数,每当进行下一次遍历的时候,队列中就存储了该层的所有元素
            int n = queue.size();
            for (int i = 0; i < n; i++) {
                //让结点出队并打印
                Node<E> node = queue.poll();
                System.out.print(node.val + " ");
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }
        //循环结束,代表一层遍历完毕
    }
    /**
     * 使用层序遍历统计一颗二叉树的结点个数
     * @param root
     * @return
     */
    public int getNodesNonRecursion(Node root){
        if (root == null){
            return 0;
        }
        int size = 0;
        Deque<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            Node cur = queue.poll();
            //计数
            size ++;
            if (cur.left != null){
                queue.offer(cur.left);
            }
            if (cur.right != null){
                queue.offer(cur.right);
            }
        }
        return size;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值