1 树形结构
- 树形结构中包含根结点和子结点,根结点就是没有前驱的结点;
- 子结点之间不能有交集,不能相交,否则不构成树形结构;
- 除了根结点之外其他结点有且仅有一个父结点;
- 一棵N个结点的树有N-1条边;
- 结点的度:一个结点含有子树的个数
- 树的度:该树的所有结点中结点度数最大的即为树的度
- 叶子结点:度为0的结点为叶子结点,也叫终端结点
树的深度和高度:树的高度针对的是整体而言,所以该图中高度为4;而深度是针对单个结点而言,就像B深度是2。一棵树的高度其实就是这棵树的最大深度。
【延伸】计算公式
一个满m叉树的高度为h,那么该满树的结点总数n = ((m的h次方) - 1) / (m-1)
例如一个满4叉树的高度为2,那么该树的结点总数n = ((4的2次方) - 1) / (4-1) = 5
最简单的2叉树的计算公式:n = 2的h次方 - 1
2 二叉树与红黑树
2.1 二叉树基本概念
注意:二叉树与树形结构的区分
1. 二叉树不存在度大于2的结点
2. 二叉树是有序的,不是大小的有序,是顺序有序,因为它有左右子树之分;每个结点都是有左右之分的
2.2 特殊的树
2.2.1 满二叉树
即每一层都放满,假如有二叉树有k层,则结点总数是(2的k次方)-1
2.2.2 完全二叉树
从根节点到最底层最右边的节点都是连续的,并且除了最后一层外,每一层都被完全填满,且所有叶子节点都尽可能向左对齐。满二叉树是一种特殊的完全二叉树。
2.2.3 平衡二叉树
空树或者任意一个节点的左子树和右子树的高度差不超过1。满二叉树和完全二叉树都是一种特殊的平衡二叉树。
2.2.4 二叉排序/搜索/查找树
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。是数据结构中的一类。在一般情况下,查询效率比链表结构要高。
时间复杂度:[O(logN), O(N)],分别对应平衡二叉树和退化成链表的两种极端情况。
2.2.5 AVL树
AVL树是最先发明的自平衡二叉查找树。AVL树本质上还是一棵二叉搜索树,它的特点是:
1.本身首先是一棵二叉搜索树。
2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。
2.2.6 B树
B树全称Balance Tree,是一种自平衡树。它和等叉树最大的不同首先表现在存储结构上,等叉树上每个节点的键值数和分叉数都是相同的,而B树则不是。如果某个B树上所有节点的分叉数最大值是m,则把这个B数叫做m阶B树。下面我们来看一下B树的具体定义:
- 所有节点最多有m个子节点。
- 非根非叶子节点至少有m/2(向上取整)个子节点。
- 根节点至少有两个子节点(除非总结点数都不够3个)。
- 所有叶子节点都在同一层。
- 任意节点如果有k个键值,则有k+1个子节点指针,键值要按照从小到大排列,子节点树上所有的键值都要在对应的两个键值之间。
2-3 B树是最简单的B树,它就是3阶B树,由于它的子节点个数是2或者3。
关于B树和2-3 B树的更多内容:深入理解红黑树(1.0)_做红黑树的题目用不用红笔写字-CSDN博客
2.2.7 B+树
关于B树、B-树、B+树的更多内容:https://www.cnblogs.com/jingzh/p/14936431.html
2.2.8 红黑树
什么是红黑树,红黑树的本质是什么?一句话就可以说清楚,红黑树是二叉树的身体、2-3 B树的灵魂,用计算机的语言来说就是,红黑树是二叉树的存储结构、2-3 B树的操作逻辑。
红黑树的性质:非红即黑、根黑叶黑、红子不红、黑径相等
- 所有的节点不是黑色的就是红色的
- 根节点是黑色的
- 所有叶子节点是黑色的
- 从每个叶子到根的所有路径上不能有两个连续的红色结点
- 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
红黑树的由来:深入理解红黑树(1.0)_做红黑树的题目用不用红笔写字-CSDN博客
红黑树的应用场景
红黑树由于其高效的查找、插入和删除操作,被广泛应用于各种需要动态数据集合的场景中,如数据库的索引、操作系统的文件系统等。
- 数据库索引:红黑树可以快速定位数据,提高数据库的查询效率。
- 文件系统:红黑树可以用于管理文件系统中的目录和文件,实现快速的查找和访问。
总的来说,红黑树是一种高效且稳定的数据结构,它通过特定的性质和操作方法,在保持树平衡的同时,提供了高效的查找、插入和删除操作。
红黑树的应用场景主要包括频繁插入删除操作和对查找效率有较高要求的场景。
红黑树是一种自平衡的二叉查找树,它能够在插入和删除操作后自动调整结构以保持平衡,从而保证了查找、插入和删除操作的效率。这种特性使得红黑树在需要频繁进行数据插入和删除操作的同时,对查找效率有较高要求的场景中表现出色。例如,在内存中的有序数据存储中,红黑树可以快速地进行增删操作,且由于内存存储不涉及I/O操作,红黑树的性能优势更加明显。此外,红黑树还适用于实现Key-Value对的数据结构,通过键值对进行查找,适用于需要快速查找特定键值对应的值的应用场景。
然而,值得注意的是,红黑树并不是在所有情况下都是最佳选择。例如,如果应用场景中对插入删除不频繁,而是对查找要求较高,AVL树可能是一个更优的选择。此外,在并发和性能有特别要求的情况下,跳跃表(skiplist)可能比红黑树更适合,因为跳跃表在实现上可能更加简单,并且在并发环境下性能更好。跳跃表具有与红黑树相似的复杂度,且在区间查找效率上更高,适合实现范围查询功能
2.2.9 线索二叉树
在二叉树的结点上加上线索的二叉树称为线索二叉树,对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化。
结点中指向前驱结点和后继结点的指针,称为线索,加上线索的二叉树称为线索二叉树。
这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
注意:线索链表解决了无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题,解决了二叉链表找左、右孩子困难的问题。
3 二叉树的性质
1. 若规定根结点的层数为1,那么一棵非空二叉树的第i层最多有2的(i-1)次方个结点【就是每个结点都存在左右两个孩子即为最多】
2. 若规定只有二叉树的根结点的深度为1,则深度为k的二叉树的最大结点数是(2的k次方)-1
3. 对于任意一棵二叉树,叶子结点的个数永远比度为2的结点个数多1。
4. 父亲结点和子节点之间相互计算(根结点为0开始编号):
(1)已知父亲结点为i,则孩子结点取值:左孩子:(2i)+1,右孩子:(2i)+2;
(2)已知孩子结点为i,则父亲结点为:(i-1)/2
问答题
1. 设根节点的深度为1,则一个拥有n个结点的二叉树深度一定在哪个区间?
答:[log以2为底(n+1)的对数,n],分别对应满二叉树、退化成链表的两种极端情况。
2. 具有n个结点的完全二叉树的深度k的值:
答:k = log以2为底(n+1)的对数向上取整,或 k = log以2为底(n)的对数向下取整
3. 具有2n个结点的完全二叉树中,叶子结点个数为n
4 遍历方式
(1)前序遍历:根左右—遇到根结点就打印;得到顺序为:ABDCEF
(2)中序遍历:左根右;得到顺序为:DBAECF
(每一个子树都要保证它的遍历方式为左根右,彻底完成才能进行下一个)
(3)后序遍历:左右根;得到顺序为:DBEFCA;
(每一个子树都要保证它的遍历方式为左根右,彻底完成才能进行下一个,即必须把左右走完才能打印根)
(4)层序遍历:按顺序从上到下,从左到右;得到顺序为:ABCDEF
注意:我们根据前序遍历顺序和后序遍历顺序并不能创造出一个二叉树,因为这两个都只能提供根结点元素,无法知道元素之间的具体位置,所以我们要创建出一个二叉树必须要一个中序遍历的顺序。
练习题
1、练习题1
2、练习题2
3、练习题3
5 二叉树构建和遍历代码
5.1 构建与前序遍历
import com.sun.org.apache.bcel.internal.generic.NEW;
import java.util.ArrayList;
import java.util.List;
public class BinaryTree {
static class TreeNode{
public char val;
public TreeNode right;
public TreeNode left;
public TreeNode(char val) {
this.val = val;
}
}
public TreeNode createTree(){
TreeNode A=new TreeNode('A');
TreeNode B=new TreeNode('B');
TreeNode C=new TreeNode('C');
TreeNode D=new TreeNode('D');
TreeNode E=new TreeNode('E');
TreeNode F=new TreeNode('F');
TreeNode G=new TreeNode('G');
TreeNode H=new TreeNode('h');
A.left=B;
A.right=C;
B.left=D;
B.right=E;
C.left=F;
C.right=G;
e.right=H;
return A;
}
public void preOrder(TreeNode root){
if(root==null) return;
System.out.println(root.val+" ");
preOrder(root.left);
preOrder(root.right);
}
public void inOrder(TreeNode root){
if(root==null) return;
inOrder(root.left);
System.out.println(root.val+" ");
inOrder(root.right);
}
public void postOrder(TreeNode root){
if(root==null) return;
postOrder(root.left);
postOrder(root.right);
System.out.println(root.val+" ");
}
public List<TreeNode> preOrder2(TreeNode root){
List<TreeNode> ret=new ArrayList<>();
if(root==null) return ret;
ret.add(root);
List<TreeNode> leftTree= preOrder2(root.left);
ret.addAll(leftTree);
List<TreeNode> rightTree=preOrder2(root.right);
ret.addAll(rightTree);
return ret;
}
public static void main(String[] args) {
BinaryTree binaryTree=new BinaryTree();
BinaryTree.TreeNode root=binaryTree.createTree();
System.out.println("========");
/* binaryTree.preOrder(root);
System.out.println();*/
binaryTree.preOrder2(root);
System.out.println();
}
}
5.2 基本操作代码
//获取树中结点的个数-方法1
public int size(TreeNode root) {
if (root == null) return 0;
int leftSize = size(root.left);
int rightSize = size(root.right);
int Size = leftSize + rightSize + 1;
return Size;
}
//获取树中结点的个数-方法2
int Size2 = 0;
public int size2(TreeNode root) {
if (root == null) return 0;
size2(root.left);
Size2++;//左右子树回去过去只计一次数就好
size2(root.right);
return Size2;
}
//求叶子结点的个数
int sizeLeaf = 0;
public int getLeafNodeCount(TreeNode root) {
if (root == null) return 0;
if (root.left == null && root.right == null) {
sizeLeaf++;
}
getLeafNodeCount(root.left);
getLeafNodeCount(root.right);
return sizeLeaf;
}
//获取第k层结点的个数
//解题思路:不是直接求第K层的结点个数,
// 而是往下移动一层,看root.left/root.right的k-1层
public int getKLevelNodeCount(TreeNode root, int k) {
if (root == null) return 0;
if (k == 1) return 1;
return (getKLevelNodeCount(root.left, k - 1) + getKLevelNodeCount(root.right, k - 1));
}
//获取二叉树的高度,即二叉树的最大深度就是该树的高度
public int getHeight(TreeNode root) {
if (root == null) return 0;
int leftTree = getHeight(root.left);
int rightTree = getHeight(root.right);
return (leftTree > rightTree ? leftTree + 1 : rightTree + 1);
}
//检测值为value的元素是否存在
public boolean find(TreeNode root,int key){
if(root==null) return false;
if(root.val==key) return true;
boolean leftTree=find(root.left,key);
if(leftTree==true){//因为find方法返回值类型为boolean,所以我们这样表示递归
return true;
}
boolean rightTree=find(root.right,key);
if(rightTree==true){
return true;
}
return false;
}
//层序遍历--普通层序遍历不需要使用递归方法,
// 也不用构建顺序表,链表去存放它的元素,直接出入队列即可实现
public void levelOrder(TreeNode root){
Queue<TreeNode> queue=new LinkedList<>();
if(root==null) return;
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur=queue.poll();
System.out.println(cur.val+" ");
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
}
//判断一棵树是不是完全二叉树
public boolean isCompleteTree(TreeNode root){
if(root==null) {
return true;
}
Queue<TreeNode> queue=new LinkedList<>();
TreeNode cur;
queue.offer(root);
while(!queue.isEmpty()){
cur=queue.poll();
if(cur!=null){
queue.offer(cur.left);//如果cur为叶子结点,那么队列中加入两个null,在执行下次循环的时候新的cur就被赋值为null,然后跳出第一个while循环
queue.offer(cur.right);
}else{
break;
}
}
while(!queue.isEmpty()){
cur=queue.poll();
if(cur!=null){
return false;
}
}
return true;
}
5.3 二叉树易错总结
1. 在用树表示的目录结构中,从根目录到任何数据文件只有唯一一条通道,因为树的特点是不相交。
2. 练习题。多叉树计算结点总数。
3. 为什么后序+中序构建二叉树是先root.left再root.right;而前序+中序构建二叉树是先root.right再root.left? 答:都必须先找到根节点。
根据上面的图片我们可以看到,中序+后序构建时,我们应先在中序中找到根节点,下一步找到的是右树,所以我们在写代码的时候要先写root,再right,最后left;前序+中序遍历构建同理,是先在前序中找到根节点root,再left,最后构建二叉树的right
5.4 递归方法使用注意
递归方法每return回去一次,它方法中所包含的变量就会被销毁掉,所以,如果我们不想该变量随着递归方法return返回而被销毁掉的话,我们一般将变量设置为成员变量(置于递归方法之外)而非局部变量(在递归方法之中)。