目录
d.传入一个二叉树的根节点root,求该树的第k层的节点个数
e.判断一棵以root为根节点的二叉树中是否包含值为val的节点
1.树型结构(查找/搜索)
1.1.概念
非线性数据结构,由n个有限节点组成一个具有层次关系的集合。(倒挂的树,根上叶下)
1.2.特点
- 有一个根节点,无前驱节点。
- 除根节点外,其余节点被分成M(M > 0)个互不相交的集合T1,T2,......Tm,其中每一个集合又是一棵与树类似的子树。Ti(1 <= i <= m)每棵子树的根节点有且只有一个前驱,可以有0个或多个后继。
- 树是递归定义的。
- 子树是不相交的。
- 除了根节点外,每个节点有且仅有一个父节点。
- 一棵N个节点的树有(N-1)条边。
1.3.小概念
- 节点的度:一个节点含有的子树的个数。
- 树的度:一棵树中,最大的节点的度。
- 叶子节点或终端节点:度为0的节点。
- 分支节点:节点的度不为0。
- 双亲节点或父节点:一个节点有子节点,这个节点称为其子节点的父节点。
- 孩子节点或子节点:一个节点含有的子树的根节点称该节点的子节点。
- 根节点:一棵树中没有双亲节点的节点。
- 节点的层次:从根开始定义起,根为第一层,根的子节点为第二层。
- 树的高度或深度:树中节点的最大层次。
- 非终端节点或分支节点:度不为0的节点。
- 兄弟节点:具有相同父节点的节点。
- 堂兄弟节点:双亲在同一层的节点互为堂兄弟。
- 节点的祖先:从根到该节点所经历分支上的所有节点。
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
- 森林:由m(m >= 0)棵互不相交的树的集合组成。
1.4.表示形式
- 双亲表示法。
- 孩子表示法。
- 孩子兄弟表示法:
class Node{
int value; //树中存储的数据
Node firstChild; //第一个孩子引用
Node nextBrother; //下一个兄弟引用
}
1.5.应用
文件系统管理(目录和文件)
1.6.常见的树结构
- 二分搜索树(BST)
- 平衡二叉树(AVL红黑树)
- 堆/并查集
- 线段树/Tire(字典树-String)- KMP算法
2.二叉树
2.1.概念
一棵二叉树是节点的一个有限集合(为空;由一个根节点加上两棵分别称为左子树和右子树的二叉树组成)
2.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。
- 度为2的节点个数 + 度为1的节点个数 + 叶子节点个数 = 总节点个数n。
- 度为2的节点个数 + 1 = 叶子节点个数。
- 规定只有根节点的二叉树的深度为1,则深度为k的二叉树的第k层上最多节点数是2 ^ (k - 1)(k >= 0)。
- 规定只有根节点的二叉树的深度为1,则深度为k的二叉树的总最大节点数是2 ^ k - 1(k >= 0)。
- 具有n个节点的完全二叉树的深度k为log2(n + 1)上取整。
- 规定根节点编号为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.二叉树的存储(每个节点彼此之间如何相互访问)
- 顺序存储
- 链式存储
- 二叉(孩子表示法)
class Node{
int val; //数据域
Node left; //左孩子的引用,常代表左孩子为根的整棵左子树
Node right; //右孩子的引用,常代表右孩子为根的整棵右子树
}
- 三叉(孩子双亲表示法)—— 平衡二叉树/AVL红黑树
class Node{
int val;
Node left;
Node right;
Node parent; //当前节点的根节点
}
2.7.二叉树的遍历
- 概念:按照一定的顺序访问这个集合的所有元素,做到不重复,不遗漏。
能使用递归的3个条件:
- 大问题能拆成小问题。
- 拆分后的问题和原问题除了数据大小不一样,解决思路完全一样。
- 存在递归终止条件。
如何写出递归程序?注意方法语义!
- 分类:
- 先序遍历(preOrder):根左右。(先访问根节点,再递归访问左子树,最后递归访问右子树)
- 中序遍历(inOrder):左根右。
- 后序遍历(postOrder):左右根。
- 层序遍历(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);
}