个人主页: 爱编程的小新~欢迎大佬们的访问
[一. 树型结构](#一. 树型结构)
[1. 树形结构的特点](#1. 树形结构的特点)
[2. 非树形结构](#2. 非树形结构)
[3. 树型结构的基本特性](#3. 树型结构的基本特性)
[4. 树的表现形式](#4. 树的表现形式)
[二. 二叉树](#二. 二叉树)
[2.1 二叉树的概念](#2.1 二叉树的概念)
[2.2 特殊的二叉树](#2.2 特殊的二叉树)
[(1) 满二叉树](#(1) 满二叉树)
[(2) 完全二叉树](#(2) 完全二叉树)
[2.3 二叉树的性质](#2.3 二叉树的性质)
[2.4 二叉树的创建](#2.4 二叉树的创建)
[2.5 二叉树的遍历](#2.5 二叉树的遍历)
[(1) 前序遍历](#(1) 前序遍历)
[(2) 中序遍历](#(2) 中序遍历)
[(3) 后序遍历](#(3) 后序遍历)
[(4) 三种遍历方式的特点](#(4) 三种遍历方式的特点)
[2.6 二叉树的基本操作](#2.6 二叉树的基本操作)
[1. 获取树中结点的个数](#1. 获取树中结点的个数)
[2. 获得叶子结点个数](#2. 获得叶子结点个数)
[3. 获得第k层结点的个数](#3. 获得第k层结点的个数)
[4. 获取二叉树的高度](#4. 获取二叉树的高度)
[5. 检测值为val的元素是否存在](#5. 检测值为val的元素是否存在)
[6. 层序遍历](#6. 层序遍历)
一. 树型结构
树型结构是一种非线性的数据结构,它是由节点和边组成的,有一个特定的节点被称为根节点,其余节点通过边连接形成的层次关系,将它称为树是因为看起来像一棵倒挂的树:
树形结构:
1. 树形结构的特点
- 层次分明:数据和结构之间存在明确的层次关系,便于表示和理解具有层次特征的信息
- 递归性:许多树形结构的操作都是通过递归的方式实现的
- 有序性:节点的子节点之间可能存在特定的顺序
- 有一个特殊的节点。称为根节点,根节点没有前驱节点
在我们的树形结构中,子树之间是不能有交集的否则就不是树形结构啦
2. 非树形结构
在树型结构的概念中:
- 子树是不相交的;
- 除了根节点外,每个节点有且只有一个父节点;
- 一棵N个节点的树有N-1条边;
但是在下图可以发现G这个节点是有两个父节点的,并且只有10个节点却有11条边,所以这不是我们意义上的树形结构
3. 树型结构的基本特性
- **结点的度:**一个结点含有子树的个数成为该结点的度;例如上图:A结点的度为3
- **树的度:**一棵树中,所有结点度的最大值称为树的度;例如上图:树的度为6
- 叶子结点或终端结点:度为0的结点称为叶子结点;例如上图:K、F、G、H、I、J就是叶子结点
- **双亲结点或父结点:**若一个结点含有子结点,则这个结点称为其子结点的父结点;例如上图:E是K的父结点
- **孩子结点或子结点:**一个结点含有的子树的根节点称为该结点的子结点:例如上图:B是A的子结点
- **根结点:**一棵树中,没有双亲结点的结点:例如上图:A结点就是根结点
- **结点的层次:**从根结点开始定义,根为第一层。根的子结点为第二层,以此类推
- **树的高度或者深度:**树中结点的最大层次;例如上图:树的高度为4
- **非终端结点或分支结点:**度不为0的结点;例如上图:B、C、D、E结点为分支结点
- **兄弟结点:**具有相同父结点的结点互称为兄弟结点;例如上图:B、C、D是兄弟结点
- **堂兄弟结点:**双亲在同一层的结点互为堂兄弟;例如上图:F、G互为堂兄弟结点
- **结点的祖先:**从根到该结点所经分支上的所有结点;例如上图:A是所有结点的祖先
- **子孙:**以某个结点为根的子树中任一结点都称为该结点的子孙;例如上图:所有结点都是A的子孙
- **森林:**由m(m>=0)课互不相交的树组成的集合称为森林
4. 树的表现形式
树型结构相对线性表来说比较复杂,有很多种表示方式:双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法等等;其中最常用的就是孩子兄弟表示法:\
class Node {
int value; // 树中存储的数据
Node firstChild; // 第一个孩子引用
Node nextBrother; // 下一个兄弟引用
}
二. 二叉树
上面我们了解了什么是树型结构,接下来看看什么是二叉树
2.1 二叉树的概念
二叉树是一种树型结构,但是二叉树中的每个结点最多有两个子结点,分别称为左结点和右结点
二叉树的特点:
- 二叉树不存在度大于2的结点
- 二叉树有左子树和右子树之分,所以次序是不能颠倒的,因此二叉树是一颗有序树
2.2 特殊的二叉树
上图演示的其实是一个最普通的二叉树,在二叉树的概念中有两种二叉树比较特殊,我们一起来看看吧
(1) 满二叉树
当一棵二叉树,如果每一层的结点都达到最大值,那么这棵树就是满二叉树:
从上图可以发现:该树的层数为3,结点总数为2(^3)-1=7,那么也就是说,如果一棵二叉树的层数为K,结点总数为2(^K)-1,那么它就是一棵满二叉树。
(2) 完全二叉树
假设一个二叉树的深度为n,那么除了第n层,其他每一层的节点数都达到了最大个数,最后一层的结点按照从左到右的顺序,依次进行排列:
满二叉树是一个特殊的完全二叉树,并且完全二叉树是效率很高的数据结构~
2.3 二叉树的性质
根据下图我们来分析一下二叉树的性质
**1.**若规定根结点的层数为1,那么一棵非空二叉树的第 i 层最多有2(^ i -1)个结点:例如上图的满二叉树,第4层有2(^4-1)=8个结点;
**2.**若规定只有根结点的二叉树深度为1,则深度为K的二叉树最大结点数是2(^k)-1:例如上图的满二叉树,深度为4,那么该树(刚好是满二叉树)的最大结点数就是2(^4)-1=15个结点;
**3.**对任何一棵二叉树,如果其叶子结点个数为n0,度为2的非叶子结点个数为n0=n2+1:
从上图可以看到:叶子结点的个数为5个,度为2的非叶子结点的个数为4个(n0=n2+1);
**4.**具有n个结点的完全二叉树的深度K为log2(n+1)上取整+1:
**5.**对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序从0开始编号,那么对于序号为 i 的结点有:
- 若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点;
- 若2i+1<n,左孩子序号:2i+1,否则无左孩子;
- 若2i+2<n,右孩子序号:2i+2,否则无右孩子;
以上就是二叉树最基本的性质啦
2.4 二叉树的创建
根据上图我们来创建一个二叉树:
public class Binary_Tree {
static class TreeNode{
public TreeNode left;//表示左孩子
public TreeNode right;//表示右孩子
char value;//表示存放数据的变量
public TreeNode(char val) {//结点的构造方法
this.value = val;
}
}
public static 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');
//连接结点
A.left=B;
A.right=C;
B.left=D;
B.right=E;
C.left=F;
C.right=G;
//返回根节点
return A;
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
}
}
那么这样一棵二叉树就创建好了,接下来我们来看看该如何遍历这棵二叉树吧
2.5 二叉树的遍历
所谓遍历 (Traversal)就是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。二叉树的遍历主要有三种方式:前序遍历、中序遍历、后序遍历,我们来看看这三种遍历方式是如何遍历的
(1) 前序遍历
前序遍历的顺序是:根节点--->左子树---->右子树的顺序进行遍历的,对于每棵子树也是按照这样的顺序进行遍历:
代码实现:
public static void preOrder(TreeNode root){
//判断结点是否为空
if(root==null)return ;
System.out.print(root.value);//打印根节点的值
//遍历左子树
preOrder(root.left);
//遍历右子树
preOrder(root.right);
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
//前序遍历二叉树
preOrder(tree);
}
运行结果:
具体的遍历过程在上述的图中已经讲过啦,这里就不在论述~
(2) 中序遍历
中序遍历的遍历顺序是: 根的左子树 ---> 根节点 ---> 根的右子树。对于每棵子树也是按照这样的顺序进行遍历:
代码实现:
public static void inOrder(TreeNode root){
//判断结点是否为空
if(root==null)return ;
//按照左子树--->根结点---->右子树的顺序进行遍历
//遍历左子树
inOrder(root.left);
//打印根节点
System.out.print(root.value);
//遍历右子树
inOrder(root.right);
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
//中序遍历二叉树
inOrder(tree);
}
运行结果:
(3) 后序遍历
后序遍历的遍历顺序是:根的左子树--->根的右子树--->根节点。对于每棵子树也是按照这样的顺序进行遍历:
代码实现:
public static void PostOrder(TreeNode root){
//判断结点是否为空
if(root==null)return;
//按照左子树-->右子树-->根节点的顺序进行遍历
//遍历左子树
PostOrder(root.left);
//遍历右子树
PostOrder(root.right);
//打印根结点
System.out.print(root.value);
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
//后序遍历二叉树
PostOrder(tree);
}
运行结果:
(4) 三种遍历方式的特点
|-------|---------|---------|---------|
| | 前序遍历 | 中序遍历 | 后序遍历 |
| 遍历顺序 | 根、左、右 | 左、根、右 | 左、右、根 |
| 遍历结果 | ABDECFG | DBEAFCG | DEBFGCA |
| 根节点位置 | 第一个 | 中间 | 最后一个 |
从表格中不难看出通过前序遍历 、后续遍历,我们能快速的知道根节点是谁,那么则可以得出结论:
前序遍历和后序遍历的结果能够确定根是谁,而中序遍历的根节点位置是在中间,说明根节点(A)的左边就是根结点的左子树元素(DBE),根结点(A)的右边就是根结点的右子树元素(FCG);
2.6 二叉树的基本操作
以下介绍的是使用二叉树的过程中一些常见的操作
1. 获取树中结点的个数
public static int size(TreeNode root){
//判断结点是否为空
if(root==null)return 0;
//如果结点不为空,那么usedsize++
//遍历左子树
int left=size(root.left);//记录左子树的结点个数
//遍历右子树
int right=size(root.right);//记录右子树的结点个数
return left+right+1;//返回左子树结点个数和右子树结点个数+1(加上当前结点)
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
System.out.println(size(tree));
}
运行结果:
2. 获得叶子结点个数
public static int getLeafTreeNodeCount(TreeNode root){
//判断结点是否为空
if(root==null)return 0;
//判断当前结点是否为叶子结点
if(root.left==null&&root.right==null){
return 1;//是就返回1
}
//不是就当前结点左子树的叶子结点个数和右子树的叶子结点个数
return getLeafTreeNodeCount(root.left)+getLeafTreeNodeCount(root.right);
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
System.out.println(getLeafTreeNodeCount(tree));
}
运行结果:
3. 获得第k层结点的个数
public static int getKLevelTreeNodeCount(TreeNode root,int k){
//判断结点是否为空或者k是否小于1
if(root==null||k<1)return 0;
//如果k=1,表示是要查找的层次,返回1
if(k==1)return 1;
//递归左右子树,直到k==1,递归到了要查找的层次
//就返回左子树的k层次的节点数+右子树的K层次的节点数==k层次总的节点数
return getKLevelTreeNodeCount(root.left,k-1)+getKLevelTreeNodeCount(root.right,k-1);
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
System.out.println(getKLevelTreeNodeCount(tree,2));
}
运行结果:
4. 获取二叉树的高度
public static int getHeight(TreeNode root){
//判断该结点是否为空
if(root==null)return 0;
//如果不为空,递归左子树和右子树,返回左子树和右子树的深度的最大值加上当前结点(深度1)
return Math.max(getHeight(root.left),getHeight(root.right))+1;
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
System.out.println(getHeight(tree));
}
运行结果:
5. 检测值为val的元素是否存在
public static TreeNode findvalue(TreeNode root,char val){
//判断结点是否为空
if(root==null)return null;
//判断当前结点的值是否等于val
if(root.value==val)return root;
//如果当前结点的值不是val,就去左子树中找
TreeNode leftNode=findvalue(root.left,val);
//如果左子树找到了就返回
if(leftNode!=null)return leftNode;
//如果左子树找不到就去右子树中找
TreeNode rightNode=findvalue(root.right,val);
//如果右子树找到了就返回
if(rightNode!=null)return rightNode;
//找不到返回null
return null;
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
System.out.println(findvalue(tree,'C').value);
}
运行结果:
6. 层序遍历
画图分析:
这里实现层序遍历需要借助队列来实现,如果对队列不熟悉的话可以看一下上一期博客:栈和队列 代码实现层序遍历:
public static void levelOrder(TreeNode root){
if(root==null)return ;
//创建队列
Queue<TreeNode> queue=new LinkedList<>();
//将根结点放入队列中
queue.offer(root);
while(!queue.isEmpty()){ //如果队列为空说明遍历完了
TreeNode cur=queue.poll();//弹出元素
System.out.print(cur.value);
//将弹出结点的孩子结点放入队列
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
}
public static void main(String[] args) {
TreeNode tree=createTree();//创建二叉树
levelOrder(tree);
}
运行结果:
以上就是二叉树的一些基本操作啦~
结语
本期的二叉树部分就到此结束啦,本篇也是2024年的最后一篇文章,祝大家在新的一年学业有成~在此感谢大家的观看!!!