一.树的重要概念:
1.树的度:树中最大节点的度(直接子孩子个数)
二叉树:在二叉树中,节点的度都不超过2
2.子树都是不相交的
3.叶子节点:树中度为0的节点
树根节点:唯一一个没有父节点的节点。
4.在任意树中,满足边长 = 顶点数 - 1
在满二叉树存在的深度中,每一层的节点个数都达到最大值。
***二叉树***:
1.节点个数N和高度(深度K,根节点为第一层的关系)
N = 2 ^ K - 1(最大节点存在于满二叉树中)
2.若深度为K,则第K层的节点最多有2 ^ (K - 1)个节点
eg:第三层的二叉树,最多存在4个节点
3.在任意二叉树中,满足度为2的节点N2和叶子节点N0的关系:N2 = N0 - 1
***完全二叉树***
1.概念:
在完全二叉树中存在两个阶段
第一阶段:每个节点的度都为2
在碰到第一个度为1的节点或叶子节点,进入第二阶段
在第二阶段,碰到的所有节点均为叶子节点
2.性质:完全二叉树中,度为1的节点要么存在且唯一,要么不存在。
若完全二叉树的节点个数为偶数,度为1的节点一定存在且唯一。
若完全二叉树的节点个数为奇数,一定不存在度为1的节点。
二.二叉树
1.二叉树的存储
任意的数据结构在计算机内部存储时都分为基于数组方式的顺序存储和基于引用方式的链式存储。
二叉树也分为顺序存储和链式存储,只有完全二叉树适合采用顺序存储(二叉树有左右之分)。
二叉树的链式存储是通过一个一个的节点引用起来的。
链式存储:左右孩子表示法和孩子双亲表示法、
重点:// 左右孩子表示法(最常使用的方法) private static class TreeNode{ int val; // 当前节点的值 Node left; // 保存左子树的树根节点 Node right; // 保存右子树的树根节点 public TreeNode(int val){ this.val = val; } }
孩子双亲表示法(树中每个节点除了保存子树节点外,还要保存其父节点的引用-》常用在平衡树的定义中)
在平衡二叉树中,任意一个子树,左右孩子的高度之差不超过1
AVL树,B树严格平衡二叉树。
RBTree,红黑节点之间严格平衡
2.二叉树的遍历
什么是对于一个数据结构的遍历?(遍历不仅仅指的是元素值的打印或输出----遍历的一种方式而已) )按照一定的规则,将该数据结构中所有元素访问一次,做到不重不漏。
对于线性数据结构链表,数组来说,遍历非常容易,要么从前向后遍历要么从后向前遍历。
在非线性结构中,如二叉树,遍历的方式有很多,其实各式各样的树形问题最终都可以看做是如何进行该树的遍历问题~~~采用不同的遍历方式,得到的结果会完全不同。
用左右孩子表示法来创建一个如上图的二叉树
普通二叉树的实现 public class MyBinTree { // 二叉树的节点类定义 private static class TreeNode{ // 当前节点的值 int val; // 左子树的树根 TreeNode left; // 右子树的树根 TreeNode right; public TreeNode(int val){ this.val = val; } } public TreeNode build() { TreeNode node1 = new TreeNode(1); TreeNode node2 = new TreeNode(2); TreeNode node3 = new TreeNode(3); TreeNode node4 = new TreeNode(4); TreeNode node5 = new TreeNode(5); TreeNode node6 = new TreeNode(6); node1.left = node2; node1.right = node4; node2.left = node3; node4.left = node5; node4.right = node6; return node1; } }
2.1 二叉树的遍历---深度优先搜索(二叉树的高度):dfs
前序遍历(这里的序指的是什么时候访问):根左右
先访问树根节点,然后递归的访问左子树,再递归的访问右子树。
所谓的前序遍历,指的是不断进行根左右的遍历方式(递归方式的遍历)。
此处的遍历就是将节点值打印输出。
前序遍历位于即将开始访问一棵树的时候
前序遍历的第一个值永远是该二叉树的树根节点
/** * 传入一颗以root为根的二叉树,就能按照前序遍历的方式进行遍历,输入每个节点的值 * @param root */ public void preOrder(TreeNode root){ // 1.base case if (root == null) { return; } // 先输出当前根节点的值 System.out.print(root.val + " "); // 继续去访问左子树并打印 preOrder(root.left); // 继续访问右子树的所有节点 preOrder(root.right); }
中序遍历:左根右
先递归的访问左子树,然后访问树根节点,再递归的访问右子树。
中序遍历左子树刚刚访问结束,马上开始右子树的访问时候。
中序遍历的特点:该树的任意节点均满足此性质
左子树的所有结果一定位于根节点的左侧
右子树的所有结果一定位于根节点的右侧
/** * 传入一颗以root为根的二叉树,就能按照中序遍历的方式进行遍历输出 * @param root */ public void inOrder(TreeNode root) { // 1.base case if (root == null) { return; } // 先访问左子树,按照中序遍历的方式 inOrder(root.left); // 中序位置,遍历当前的树根并输出 System.out.print(root.val + " "); // 继续访问右子树 inOrder(root.right); }
后序遍历:左右根
先递归访问左子树,再递归访问右子树,最后访问树根节点。
后序遍历位于即将离开一棵树的时候。
后序遍历:最后的结果为整棵树的树根节点。
后序结果集reverse(倒置)就得到前序遍历的镜像结果---》即为根右左
/** * 传入一颗以root为根的二叉树,就能按照后序遍历的方式进行遍历输出 * @param root */ public void postOrder(TreeNode root) { if (root == null) { return; } // 先访问左子树 postOrder(root.left); // 再访问右子树 postOrder(root.right); // 最后访问根节点 System.out.print(root.val + " "); }
2.2 二叉树的遍历---广度优先搜索(二叉树的宽度):bfs
层序遍历:从二叉树的树根开始不断地从上至下,从左至右的遍历。
层序遍历的实现需要借助队列结构,先将根节点入队,依次将左右子树入队,出队顺序就是从上之下,从左至右。
2.3 练习
1.某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为(A)
A: ABDHECFG B: ABCDEFGH C: HDBEAFCG D: HDEBFGCA
完全二叉树(所有节点靠左排列)
2.二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为(A)
A: E B: F C: G D: H
解析:前序遍历:根左右
3.设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为(D)
A: adbce B: decab C: debac D: abcde
解析:中序遍历序列:badce,后序遍历序列:bdeca。
后序遍历(左右根) reverse-》acedb(根右左)
左右根,不断去找二叉树的树根节点在哪。拿着这个树根节点去寻找中序遍历的位置,左子树是谁,右子树是谁,递归上面过程即可还原一颗二叉树。
4.某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为(A)
A: FEDCBA B: CBAFED C: DEFCBA D: ABCDEF
3.二叉树的基本操作
3.1 二叉树的节点个数
需要将问题拆解,拆解成左右子树问题+当前树根的问题。
3.2 二叉树的叶子节点个数
同样需要将问题拆解,拆解成左右子树问题+当前树根的问题。当root本身就是空,那么就没有叶子结点直接返回0。如果有树根结点并且它的左右孩子结点都为空那么就只有一个叶子结点,仍然用递归实现。
/**
* 传入一颗以root为根的二叉树,就能求出该二叉树的叶子节点个数并返回
* @param root
* @return
*/
public int getLeafNodes(TreeNode root) {
// 1.base case 空树,没有叶子节点
if (root == null) {
return 0;
}
// 2.base case 当前只有一个树根节点,唯一的节点,也就是叶子节点
if (root.left == null && root.right == null) {
return 1;
}
// 此时root一定不为空,且有子树
// 整棵树的叶子节点个数 = 左子树的叶子节点个数 + 右子树的叶子节点个数
return getLeafNodes(root.left) + getLeafNodes(root.right);
}
public static void main(String[] args) {
MyBinTree tree = new MyBinTree();
TreeNode root = tree.build();
System.out.println("二叉树的节点个数为 : ");
int num = tree.getNodes(root);
System.out.println(num);
System.out.println("二叉树的叶子节点个数为 : ");
System.out.println(tree.getLeafNodes(root));
3.3 二叉树的高度
当前只知道root的情况,如果root == null =》 0层。如果root != null,树根就是第一层。求出左右子树的高度之后,取它两的最大值 + 1(树根这一层)。
3.4 求第K层的节点个数
给定一颗以root为树根的二叉树,求出第K层的节点个数。 K <= height
所有二叉树的问题归根到底就是遍历 + 拆解成左右子树的问题!
3.5 判断二叉树中是否存在指定值的元素
首先判断二叉树为空的情况,如果为空一定不存在指定值的元素。
4.总结
二叉树的几乎所有问题归根到底还是遍历问题。