点击上方「10分钟编程」关注我呦
让我们在一起每天「博学」一点点,成为更好的自己!
二叉树
1、问题描述
树是一种数据结构,也是用于存储的,在已有数组、链表的情况下,为什还需要树呢?它有什么优点?那么我们先看一下数组、链表都有什么特点。
-
数组
优点:根据下标检索时速度很快,当序列有序时,可以采用二分查找、斐波那契查找提高检索效率
缺点:插入(扩容、移动等操作)、删除(移动)效率低下。
-
链表
优点:删除、插入时效率高,不需要连续的存储空间
缺点:检索时效率低
那么,有没有一种数据结构同时具有数组和链表的优点呢?来,我们看一下的树的特点。
-
树
可以提高存储、读取的效率,如二叉排序树(Binary Sort tree),即可以保证数据的检索速度,同时也可以保证数据的插入、删除、修改的速度。
在学习二叉树之前,我们先学习下树。
2、树的概念
-
结点
-
根结点,A是所有节点的根节点,一棵树只有一个根结点
-
父结点,A是结点B和结点C的父结点,B是结点D和结点E的父结点
-
子结点,结点B和结点C是结点A的子结点
-
叶子结点,没有子结点的结点
-
结点的权,结点的值,
-
路径,从某一个节点到另一结点的路线
-
层,如图,共4层
-
子树,结点D和结点H可以构成子树,其子树的根节点是D
-
树的高度,最大层数
-
森林,多颗子树构成森林
3.二叉树
定义:每个结点最多只能有两个子结点(左结点和右结点)的形式
3.1满二叉树
定义:所有的叶子结点都在最后一层,结点数为2^n-1,n为层数
3.2完全二叉树
定义:如果二叉树的所有叶子结点都在最后一层或者倒数第二层,最后一层的叶子结点左边连续,倒数第二层的叶子结点右边连续,称为完全二叉树
3.4二叉树遍历
-
前序遍历
特点:先输出父结点,在输出左结点、最后输出右结点
代码实现
public void preOrder() {
System.out.println(this);
if (this.leftNode != null) {
this.leftNode.preOrder();
}
if (this.rightNode != null) {
this.rightNode.preOrder();
}
}
-
中序遍历
特点:先输出左结点,再输出父结点,最后输出右结点
代码实现
public void infixOrder() {
if (this.leftNode != null) {
this.leftNode.infixOrder();
}
System.out.println(this);
if (this.rightNode != null) {
this.rightNode.infixOrder();
}
}
-
后序遍历
特点:先输出左结点,再输出右结点,最后输出父结点
public void postOrder() {
if (this.leftNode != null) {
this.leftNode.postOrder();
}
if (this.rightNode != null) {
this.rightNode.postOrder();
}
System.out.println(this);
}
3.4二叉树查找指定结点
-
前序遍历查找
public PersonNode preOrderSearch(int no) {
//判断当前节点是否为要查找的节点
if (this.no == no) {
return this;
}
//判断当前节点的左节点是否为空,不为空时继续左递归查找
PersonNode resNode = null;
if (this.leftNode != null) {
resNode = this.leftNode.preOrderSearch(no);
}
//判断左递归后查询的节点是为空
if (resNode != null) {
return resNode;
}
//若左递归查询无果,则向右递归,直接返回结果
if (this.rightNode != null) {
resNode = this.rightNode.preOrderSearch(no);
}
return resNode;
}
-
中序遍历查找
public PersonNode infixOrderSearch(int no) {
PersonNode resNode = null;
//判断当前节点的左节点是否为空,不为空时进行中序递归判断
if (this.leftNode != null) {
resNode = this.leftNode.infixOrderSearch(no);
}
//判断递归后的值是否为空,不为空,则证明在左子树上找到
if (resNode != null) {
return resNode;
}
//判断当前结点是否为自己想要查找的
if (this.no == no) {
return this;
}
//若坐左子树和当前结点都不是我们要查询的,则判断右结点是否为空,不为空时,则进行中序查找递归
if (this.rightNode != null) {
resNode = this.rightNode.infixOrderSearch(no);
}
return resNode;
}
-
后序遍历查找
public PersonNode postNodeSearch(int no) {
PersonNode resNode = null;
//判断当前结点的左结点是否存在,若存在则进行左结点的后续遍历查找递归
if (this.leftNode != null) {
resNode = this.leftNode.postNodeSearch(no);
}
//左子树查找到
if (resNode != null) {
return resNode;
}
//若没有左子树查找到,则判断右结点是否存在,存在时进行后序遍历查找递归
if (this.rightNode != null) {
resNode= this.rightNode.postNodeSearch(no);
}
//右子树查找到
if(resNode!=null){
return resNode;
}
//判断当前节点是否为要查找的结点
if (this.no == no) {
resNode= this;
}
return resNode;
}
3.5二叉树指定结点的删除
前提:先判断根结点,根结点的判断不在此代码中。
public void delNode(int no){
//判断左边结点是否为空,非空时判断是否为要查找的结点
if(this.leftNode!=null&&this.leftNode.no==no){
this.leftNode=null;
}
//判断右边结点是否为空,非空时判断是否为要查找的结点
if(this.rightNode!=null&&this.rightNode.no==no){
this.rightNode=null;
}
//判断左子结点是非为空,不为空时进行递归查找删除
if(this.leftNode!=null){
this.leftNode.delNode(no);
}
//判断右子结点是非为空,不为空时进行递归查找删除
if(this.rightNode!=null){
this.rightNode.delNode(no);
}
}
3.5顺序存储二叉树
概念:从数据存储上看,数组存储的方式与树的存储方式可以相互转换
给定一数组,完成二叉树的遍历
顺序存储二叉树通常只考虑完全二叉树,n为该节点在数组中的下标。
1、第n个元素的左子结点为n*2+1
2、第n个元素的右子结点为n*2+2
3、第n个元素的父结点为(n-1)/2
-
前序遍历
public void preOrder(int index) {
if (arr != null && arr.length > 0) {
System.out.println(arr[index]);
if (index * 2 + 1 < arr.length) {
//在左结点递归
preOrder(index * 2 +1);
}
if (index * 2 + 2 < arr.length) {
//在右结点递归
preOrder(index * 2 +2);
}
}
}
-
中序遍历
public void infixOrder(int index) {
if (arr != null && arr.length > 0) {
if (index * 2 + 1 < arr.length) {
//在左结点递归
infixOrder(index * 2 +1);
}
System.out.print(arr[index]);
if (index * 2 + 2 < arr.length) {
//在右结点递归
infixOrder(index * 2 +2);
}
}
}
-
后序遍历
public void postOrder(int index) {
if (arr != null && arr.length > 0) {
if (index * 2 + 1 < arr.length) {
//在左结点递归
postOrder(index * 2 +1);
}
if (index * 2 + 2 < arr.length) {
//在右结点递归
postOrder(index * 2 +2);
}
System.out.print(arr[index]);
}
}
3.6线索化二叉树的构造
概念:有N个结点的二叉树会存在N+1个空指针,可以将这些空指针理解为线索(每个结点有2个指针,共2N个指针,将N个结点连接起来需要N-1个指针,所以剩余指针为:2N-(N-1)=N+1)。而二叉树线索化的过程中,会利用树中的空指针作为寻找当前结点的前驱和后继的线索。为了区分树中的指针是指向子树还是线索,定义了leftType和rightType标识符,其意义如下:
如果leftType为0,则表示指向左子结点;若leftType=1,则表示指向前驱结点。
如果rightType为0,则表示指向右子结点;若leftType=1,则表示指向后继结点。
图解如下:图解采用采用中序遍历的方式构造线索化二叉树,同理,有前序构造二叉树和后序构造二叉树
蓝色实线代表左右子树,红色虚线代表前继节点,绿色虚线代表后继结点。
//定义当前结点的前驱结点
PersonNode pre=null;
//参数中的node为需要线索化的结点
public void threadedNodes(PersonNode node){
if(node==null){
return;
}
//线索化左子树
threadedNodes(node.getLeftNode());
//线索化当前结点
if(node.getLeftNode()==null){
//让当前结点的左指针指向前驱结点
node.setLeftNode(pre);
//修改当前结点的左指针类型
node.setLeftNodeType(1);
}
//处理后继结点
if(pre!=null&&pre.getRightNode()==null){
//让前驱结点的右指针指向当前结点
pre.setRightNode(node);
//修改前驱结点的右指针类型
pre.setRightNodeType(1);
}
//每处理一个结点后,让当前结点是下一个结点的前驱结点。
pre=node;
//线索化右子树
threadedNodes(node.getRightNode());
}
3.7线索化二叉树的查找
线索化二叉树后,各个结点的指向均会发生变化,原来的遍历(前序、中序和后序)方式不能在使用。我们需要使用新的遍历方法。可以采用线性查找的遍历的方法,不需要再使用递归的方法。
修改中序遍历方法遍历线索化二叉树
public void listThreadedBinaryTree() {
PersonNode node = root;
while (node != null) {
while (node.getLeftNodeType() == 0) {
node = node.getLeftNode();
}
System.out.println(node);
while (node.getRightNodeType() == 1) {
node = node.getRightNode();
System.out.println(node);
}
node = node.getRightNode();
}
}
笔者组建了技术交流群,关注公众号,欢迎大家加入,一起进步。
在公众号回复success领取独家整理的学习资源
看了这篇文章,你是否「博学」了
「扫码关注我」,每天博学一点点。