30-二叉树

目录

1.树型结构(查找/搜索)

1.1.概念

1.2.特点

1.3.小概念

1.4.表示形式

1.5.应用

1.6.常见的树结构

2.二叉树

2.1.概念

2.2.特点

2.3.基本形态

2.4.两种特殊的二叉树

1)满二叉树

2)完全二叉树

2.5.二叉树的性质

2.6.二叉树的存储(每个节点彼此之间如何相互访问)

2.7.二叉树的遍历

2.8.代码实现

2.9.扩展问题

a.传入一个二叉树的根节点root,求该树的节点个数

b.传入一个二叉树的根节点root,求该树的叶子节点个数

c.传入一个二叉树的根节点root,求该树的高度

d.传入一个二叉树的根节点root,求该树的第k层的节点个数

e.判断一棵以root为根节点的二叉树中是否包含值为val的节点


1.树型结构(查找/搜索)

1.1.概念

非线性数据结构,由n个有限节点组成一个具有层次关系的集合。(倒挂的树,根上叶下)

1.2.特点

  1. 有一个根节点,无前驱节点。
  2. 除根节点外,其余节点被分成M(M > 0)个互不相交的集合T1,T2,......Tm,其中每一个集合又是一棵与树类似的子树。Ti(1 <=  i <= m)每棵子树的根节点有且只有一个前驱,可以有0个或多个后继。
  3. 树是递归定义的。
  4. 子树是不相交的。
  5. 除了根节点外,每个节点有且仅有一个父节点。
  6. 一棵N个节点的树有(N-1)条边。

1.3.小概念

  1. 节点的度:一个节点含有的子树的个数。
  2. 树的度:一棵树中,最大的节点的度。
  3. 叶子节点或终端节点:度为0的节点。
  4. 分支节点:节点的度不为0。
  5. 双亲节点或父节点:一个节点有子节点,这个节点称为其子节点的父节点。
  6. 孩子节点或子节点:一个节点含有的子树的根节点称该节点的子节点。
  7. 根节点:一棵树中没有双亲节点的节点。
  8. 节点的层次:从根开始定义起,根为第一层,根的子节点为第二层。
  9. 树的高度或深度:树中节点的最大层次。
  10. 非终端节点或分支节点:度不为0的节点。
  11. 兄弟节点:具有相同父节点的节点。
  12. 堂兄弟节点:双亲在同一层的节点互为堂兄弟。
  13. 节点的祖先:从根到该节点所经历分支上的所有节点。
  14. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  15. 森林:由m(m >= 0)棵互不相交的树的集合组成。

1.4.表示形式

  1. 双亲表示法。
  2. 孩子表示法。
  3. 孩子兄弟表示法:
class Node{
    int value; //树中存储的数据
    Node firstChild; //第一个孩子引用
    Node nextBrother; //下一个兄弟引用
}

1.5.应用

文件系统管理(目录和文件)

1.6.常见的树结构

  1. 二分搜索树(BST)
  2. 平衡二叉树(AVL红黑树)
  3. 堆/并查集
  4. 线段树/Tire(字典树-String)- KMP算法

2.二叉树

2.1.概念

一棵二叉树是节点的一个有限集合(为空;由一个根节点加上两棵分别称为左子树和右子树的二叉树组成)

2.2.特点

  1. 每个节点最多有2棵子树,二叉树的一个节点的最大度为2。
  2. 二叉树的子树有左右之分(次序不能颠倒),二叉树是有序树。

2.3.基本形态

2.4.两种特殊的二叉树

1)满二叉树

每一层的节点数都达到最大值。层数为k,节点总数是2 ^ k - 1。是一种特殊的完全二叉树。

2)完全二叉树

对于深度为k,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中编号从1至n的节点一一对应。

若一个节点有右树,必然有左树。相当于满二叉树缺了右半边。

  • 度为2的节点有N个。
  • 度为1的节点最多有1个,只有左树的那一个节点。
  • 度为0的节点有N个。

是一种效率很高的数据结构。

  • 只有完全二叉树可以使用顺序表(数组)存储(没有浪费空间),即将二叉树用层序遍历方式放入数组中,这种方式的主要用法就是堆的表示。
  • 其他都是链式表示(通过引用)。

2.5.二叉树的性质

  1. 边长 = 节点个数 - 1。
  2. 度为2的节点个数 + 度为1的节点个数 + 叶子节点个数 = 总节点个数n。
  3. 度为2的节点个数 + 1 = 叶子节点个数。
  4. 规定只有根节点的二叉树的深度为1,则深度为k的二叉树的第k层上最多节点数是2 ^ (k - 1)(k >= 0)。
  5. 规定只有根节点的二叉树的深度为1,则深度为k的二叉树的总最大节点数是2 ^ k - 1(k >= 0)。
  6. 具有n个节点的完全二叉树的深度k为log2(n + 1)上取整。
  7. 规定根节点编号为0,则对一个节点为i的节点来说(编号按从上至下,从左至右的顺序),若存在左树,右树,父节点:
  • 左树节点编号为:2i + 1
  • 右树节点编号为:2i + 2
  • 父节点编号为:(i - 1) / 2

eg:二叉树中,第6层叶子节点个数为9,该二叉树最多有多少个节点?

解:一定有第7层,第6层一共有2 ^ (6 - 1) = 32个节点,非叶子节点个数为32 - 9 = 23个,

第7层节点个数取决于第6层的非叶子节点(度为2的节点),第7层节点个数 = 23 * 2 = 46个。

因为是完全二叉树,不可能有第8层。

故整棵树的节点个数 = (2 ^ 5 - 1) + 32 + 46 =63 + 46 = 109个。

2.6.二叉树的存储(每个节点彼此之间如何相互访问)

  1. 顺序存储
  2. 链式存储
  • 二叉(孩子表示法)
class Node{
    int val; //数据域
    Node left; //左孩子的引用,常代表左孩子为根的整棵左子树
    Node right; //右孩子的引用,常代表右孩子为根的整棵右子树
}
    
  • 三叉(孩子双亲表示法)—— 平衡二叉树/AVL红黑树
class Node{
    int val;
    Node left;
    Node right;
    Node parent; //当前节点的根节点
}

2.7.二叉树的遍历

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

能使用递归的3个条件:

  1. 大问题能拆成小问题。
  2. 拆分后的问题和原问题除了数据大小不一样,解决思路完全一样。
  3. 存在递归终止条件。

如何写出递归程序?注意方法语义!

  • 分类:
  1. 先序遍历(preOrder):根左右。(先访问根节点,再递归访问左子树,最后递归访问右子树)
  2. 中序遍历(inOrder):左根右。
  3. 后序遍历(postOrder):左右根。
  4. 层序遍历(levelOrder):按照树的深度一层一层由上向下,由左向右访问节点。

其中:先序,中序,后序遍历是使用栈,采用递归的方式来实现的;而后序遍历则是使用队列,采用迭代的方式来实现的。

  • 先序遍历的第一个访问的是根节点,后序遍历的最后一个访问的是根节点。
  • 中序和后序遍历的第一个节点是当前树的最左侧节点。
  • 中序遍历左子树的遍历结果出现在根节点左侧,右子树的遍历结果出现在根节点右侧。
  • 由“前序 + 中序”和“后序 + 中序”可知二叉树的结构(必有中序),“先序 + 后序”不行。这是在不给空结点(#)的条件下。

2.8.代码实现

public class MyBinaryTree {
    /**
     * 孩子兄弟表示法
     */
    private static class Node { //static表示私有静态内部类,内部类不需要依赖外部类的创建
        private char val;
        private Node left;
        private Node right;

        public Node(char val) {
            this.val = val;
        }
    }

    /**
     * 生成一个测试二叉树
     * @return
     */
    public Node buildATree() {
        Node nodeA = new Node('A');
        Node nodeB = new Node('B');
        Node nodeC = new Node('C');
        Node nodeD = new Node('D');
        Node nodeE = new Node('E');
        Node nodeF = new Node('F');
        Node nodeG = new Node('G');
        Node nodeH = new Node('H');
        nodeA.left = nodeB;
        nodeA.right = nodeC;
        nodeB.left = nodeD;
        nodeB.right = nodeE;
        nodeE.left = nodeG;
        nodeG.right = nodeH;
        nodeC.right = nodeF;
        return nodeA;
    }

    /**
     * 先序遍历——传入一个二叉树的根节点,根据先序遍历访问该树
     * @param root
     */
    public void preOrder(Node root) {
        if(root == null) {
            return;
        }
        System.out.print(root.val + " ");
        preOrder(root.left);
        preOrder(root.right);
    }

    /**
     * 中序遍历
     * @param root
     */
    public void inOrder(Node root) {
        if(root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
    }

    /**
     * 后序遍历
     * @param root
     */
    public void postOrder(Node root) {
        if(root == null) {
            return;
        }
        postOrder(root.left);
        postOrder(root.right);
        System.out.print(root.val + " ");
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        MyBinaryTree myBinaryTree = new MyBinaryTree();
        Node root = myBinaryTree.buildATree();
        System.out.println("先序遍历结果:");
        myBinaryTree.preOrder(root);
        System.out.println("\n中序遍历结果:");
        myBinaryTree.inOrder(root);
        System.out.println("\n后序遍历结果:");
        myBinaryTree.postOrder(root);
    }
}

2.9.扩展问题

a.传入一个二叉树的根节点root,求该树的节点个数

/**
 * 传入一个二叉树的根节点root,求该树的节点个数
 * @param root
 * @return
 */
public int getNodeCount(Node root) {
    if(root == null) {
        return 0;
    }
    //已知根节点->1,剩下节点个数 = 左树节点个数 + 右树节点个数
    return 1 + getNodeCount(root.left) + getNodeCount(root.right);
}

b.传入一个二叉树的根节点root,求该树的叶子节点个数

/**
 * 传入一个二叉树的根节点root,求该树的叶子节点个数
 * @param root
 * @return
 */
public int getLeafNodeCount(Node root) {
    if(root == null) {
        return 0;
    }
    if(root.left == null && root.right == null) {
        return 1;
    }
    //此时二叉树不空且有子树,二叉树叶子节点个数 = 左树叶子节点个数 + 右树叶子节点个数
    return getLeafNodeCount(root.left) + getLeafNodeCount(root.right);
}
    

c.传入一个二叉树的根节点root,求该树的高度

/**
 * 传入一个二叉树的根节点root,求该树的高度
 * @param root
 * @return
 */
public int height(Node root) {
    if(root == null) {
        return 0;
    }
    return 1 + Math.max(height(root.left), height(root.right));
}

d.传入一个二叉树的根节点root,求该树的第k层的节点个数

/**
 * 传入一个二叉树的根节点root,求该树的第k层的节点个数
 * @param root
 * @param k
 * @return
 */
public int getKLevelNodes(Node root, int k) {
    //先写边界条件
    //不合法的条件
    if(k <= 0 || root == null) {
        return 0;
    }
    //不借助任何其他函数就能得到的值
    if(k == 1) {
        return 1;
    }
    return getKLevelNodes(root.left, k - 1) + getKLevelNodes(root.right, k - 1);
}

e.判断一棵以root为根节点的二叉树中是否包含值为val的节点

/**
 * 判断一棵以root为根节点的二叉树中是否包含值为val的节点
 * @param root
 * @param val
 * @return
 */
public boolean contains(Node root, char val) {
    if(root == null) {
        return false;
    }
    if(root.val == val) {
        return true;
    }
    return contains(root.left, val) || contains(root.right, val);
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值