这篇文章讲解关于树的一系列知识,后续可能会根据知识点的数量分开写成多个文章,你可以从中获取如下知识:
- 对之前已有的存储方式回顾
- 对树有个简单的了解
- 如果有看见节点和结点都表达的同一个意思
二叉树的系列文章:
- 什么是二叉树(增删改查):https://blog.csdn.net/weixin_46635575/article/details/121121492
- 二叉树的顺序存储:https://blog.csdn.net/weixin_46635575/article/details/121188680
- 线索化二叉树:https://blog.csdn.net/weixin_46635575/article/details/121189266
- 树之赫夫曼树:https://blog.csdn.net/weixin_46635575/article/details/121269506
- 赫夫曼编码:https://blog.csdn.net/weixin_46635575/article/details/121295353
- 二叉排序树:https://blog.csdn.net/weixin_46635575/article/details/121443963
- 多叉树和B树:https://blog.csdn.net/weixin_46635575/article/details/121477970
问题引入:为什么需要树这种结构呢?
一、已知的存储方式回顾
1、数组的优点与缺点
- 可以方便查找,排序等
- 当他数据满之后,当要插入数据的,都会降低效率。
- 比如我们的ArrayList这个类,它也是按一定的比例来扩容。
2、链表的优点与缺点
- 链表摒弃了数组的浪费内存,但是它仍然存在检索比较浪费时间的。
3、需要原因
- 能提高数据存储,读取得效率,比如利用了二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时可以保证数据的插入,删除,修改的速度。
二、以二叉排序数来介绍
不过也包括了很多的B树什么的,还有很多。
1、性能分析
我感觉它其实和hash差不多,都是把其分为不同分链路,然后提高了性能。
2、树示意图
3、二叉树的概念
1)树有多重,每一个节点最低哦只能有两个子节点的一种形式成为二叉树
2)二叉树的子节点分为左右节点。
3)二叉树的所有叶子节点都在最后一层,并且节点总数 = 2^n - 1,n为层数,则称为满二叉树
4)如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边是连续的,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
三、二叉树的遍历
1、先来了解一下概念和思路
(1)前序遍历
-
概念:先输出父节点,在遍历左子树和右子树
-
思路分析
(2)中序遍历
- 先遍历左子树,再遍历父节点,最后遍历右子树
- 思路分析
(3)后续遍历
- 先遍历左节点,在遍历右节点,最后遍历父节点
- 思路分析
总结:总的来说都是按着父节点的遍历顺序来参考的。
2、遍历的代码实现。
总体来说,一步步跟着看,是没问题的
- 首先看HeroNode类的编写
- 然后再看BinnaryTree
- 最后再看树的创建
目的:
(1)初略版
package cn.mldn;
import java.util.ArrayList;
public class TreeTest {
public static void main(String[] args) {
//首先要创建二叉树
BinaryTree binaryTree = new BinaryTree();
HeroNode heroNode1 = new HeroNode(1, "松江2");
HeroNode heroNode2 = new HeroNode(2, "松江2");
HeroNode heroNode3 = new HeroNode(3, "松江3");
HeroNode heroNode4 = new HeroNode(4, "松江4");
//以上节点虽然创建了,但是并没有什么关系,
//以下是手动创建二叉树来使用,后续再来完成代码
binaryTree.setRoot(heroNode1);
heroNode1.setLeft(heroNode2);
heroNode1.setRight(heroNode3);
heroNode3.setRight(heroNode4);
//测试一下前序遍历
binaryTree.preOrder();
System.out.println("+++++++++++");
//测试一下后续遍历
binaryTree.infixOrder();
System.out.println("+++++++++++");
//后续遍历
binaryTree.postOrder();
}
}
//定义一个树的类
class BinaryTree {
//首先,这个树里面最重要的是根节点
private HeroNode root;
public void setRoot(HeroNode heroNode) {
this.root = heroNode;
}
//前序遍历
public void preOrder() {
if (this.root != null) {
this.root.preOrder();
} else {
System.out.println("当前二叉树为空,无法遍历");
}
}
//中序遍历
public void infixOrder() {
if (this.root != null) {
this.root.infixOrder();
} else {
System.out.println("当前二叉树为空,无法遍历");
}
}
//后续遍历
public void postOrder() {
if (this.root != null) {
this.root.postOrder();
} else {
System.out.println("当前二叉树为空,无法遍历");
}
}
}
//先创建节点
class HeroNode {
private int no;
private String name;
private HeroNode left;//左边节点,默认为null
private HeroNode right;//右边节点,默认为null
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'';
}
public void setNo(int no) {
this.no = no;
}
public void setName(String name) {
this.name = name;
}
//前序遍历的方法
/**
* //前序遍历的方法
* 首先这个方法写在节点类的里面就要好好思考了
*
*/
public void preOrder() {
System.out.println(this);//先输出父节点
if (this.left != null) {//向左子树递归
this.left.preOrder();
}
//递归向右子子树前序遍历
//为什么不是else if呢,而是if呢,因为全部树都要遍历,肯定不能是要么遍历左边,要么遍历右边的
if (this.right != null) {
this.right.preOrder();
}
}
//中序遍历
public void infixOrder() {
//首先递归向左子树
if (this.left != null) {
this.left.infixOrder();
}
//输出当前节点
System.out.println(this);
//向右边递归
if (this.right != null) {
this.right.infixOrder();
}
}
//后续遍历
public void postOrder() {
if (this.left != null) {//向左递归
this.left.postOrder();
}
if (this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
public void setLeft(HeroNode left) {
this.left = left;
}
public void setRight(HeroNode right) {
this.right = right;
}
}
(2)总体实现版
总体实现版本:先往下面看,先把遍历什么的都看完了,再回来看。
四、二叉树的查找
1、问题引入
(1)问题
二叉树查找指定节点
- 请编写前序查找,中序查找和后续查找的方法
- 并分别使用三种查找方式,查找HeroNode = 5的节点
- 并分析各种查找方式,分别比较多少次
(2)思路分析
2、代码实现
(1)代码如下
package cn.mldn;
import java.util.ArrayList;
public class TreeTest {
public static void main(String[] args) {
//首先要创建二叉树
BinaryTree binaryTree = new BinaryTree();
HeroNode heroNode1 = new HeroNode(1, "松江2");
HeroNode heroNode2 = new HeroNode(2, "松江2");
HeroNode heroNode3 = new HeroNode(3, "松江3");
HeroNode heroNode4 = new HeroNode(4, "松江4");
//以上节点虽然创建了,但是并没有什么关系,
//以下是手动创建二叉树来使用,后续再来完成代码
binaryTree.setRoot(heroNode1);
heroNode1.setLeft(heroNode2);
heroNode1.setRight(heroNode3);
heroNode3.setRight(heroNode4);
/*//测试一下前序遍历
binaryTree.preOrder();
System.out.println("+++++++++++");
//测试一下后续遍历
binaryTree.infixOrder();
System.out.println("+++++++++++");
//后续遍历
binaryTree.postOrder();*/
System.out.println(binaryTree.infixOrderSearch(3));
System.out.println(binaryTree.postOrderSearch(3));
System.out.println(binaryTree.preOrderSearch(3));
}
}
//定义一个树的类
class BinaryTree {
//首先,这个树里面最重要的是根节点
private HeroNode root;
public void setRoot(HeroNode heroNode) {
this.root = heroNode;
}
//前序遍历查找
public HeroNode preOrderSearch(int no) {
if (root != null) {
return root.preOrderSearch(no);
} else {
return null;
}
}
//中序遍历查找
public HeroNode infixOrderSearch(int no) {
if (root != null) {
return root.infixOrderSearch(no);
} else {
return null;
}
}
//后序遍历查找
public HeroNode postOrderSearch(int no) {
if (root != null) {
return root.postOrderSearch(no);
} else {
return null;
}
}
}
//先创建节点
class HeroNode {
private int no;
private String name;
private HeroNode left;//左边节点,默认为null
private HeroNode right;//右边节点,默认为null
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'';
}
public void setNo(int no) {
this.no = no;
}
public void setName(String name) {
this.name = name;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public void setRight(HeroNode right) {
this.right = right;
}
/**
* 前序遍历查找的方法
* @param no 查找的on
* @return 找到返回,找不到返回null
*/
public HeroNode preOrderSearch(int no) {
//1、比较当前节点是不是
if (this.no == no) {
return this;
}
//判断当前节点是否为空,就像前序遍历一样的查找
HeroNode current = null;//零时变量用来保存找到的值
if (this.left != null) {
current = this.left.preOrderSearch(no);
}
//2、向左边查找
if (current != null) {//说明从前序遍历的左子树上找到了
return current;
}
//如果左边没有找到,则向右边递归
if (this.right != null) {
current = this.right.preOrderSearch(no);
}
//3、向右边查找
return current;
}
/**
* 中序遍历的查找
* @param no
* @return
*/
public HeroNode infixOrderSearch(int no) {
//1、判断当前节点的左子节点是否为空
HeroNode current = null;
if (this.left != null) {
current = this.left.infixOrderSearch(no);
}
if (current != null) {//说明中序遍历时,向前遍历的时候找到了
return current;
}
//2、如果没有找到则判断当前节点是否相等
if (this.no == no) {
return this;
}
//3、如果当前节点和向前遍历都没有扎到,则到像后面找
if (this.right != null) {
current = this.right.infixOrderSearch(no);
}
return current;
}
public HeroNode postOrderSearch(int no) {
//1、先判断左递归查找
HeroNode current = null;
if (this.left != null) {
current = this.left.postOrderSearch(no);
}
if (current != null) {//说明在左子树找到
return current;
}
//2、向右子树查找
if (this.right != null) {
current = this.right.postOrderSearch(no);
}
if (current != null) {
return current;
}
//3、如果左右都没有找到,判断当前节点是否等于
if (this.no == no) {
return this;
} else {
return current;
}
}
}
(2)遍历次数
以上面为例:
- 前序遍历查找:4次
- 中序遍历查找:3次
- 后续遍历查找:2次
五、二叉树的删除节点
1、删除思路
(1)先考虑简单的情况
非叶子节点的时候,情况后续文章会细入的讲解一下
- 如果是叶子节点是叶子节点,则删除该节点
- 如果删除的节点是非叶子节点,则删除该子树
- 测试,删除掉5号子节点和3号节点
(2)思路规定
2、代码实现
package cn.mldn;
import java.util.ArrayList;
public class TreeTest {
public static void main(String[] args) {
//首先要创建二叉树
BinaryTree binaryTree = new BinaryTree();
HeroNode heroNode1 = new HeroNode(1, "松江2");
HeroNode heroNode2 = new HeroNode(2, "松江2");
HeroNode heroNode3 = new HeroNode(3, "松江3");
HeroNode heroNode4 = new HeroNode(4, "松江4");
HeroNode heroNode5 = new HeroNode(5, "松江5");
//以上节点虽然创建了,但是并没有什么关系,
//以下是手动创建二叉树来使用,后续再来完成代码
binaryTree.setRoot(heroNode1);
heroNode1.setLeft(heroNode2);
heroNode1.setRight(heroNode3);
heroNode3.setRight(heroNode4);
heroNode3.setLeft(heroNode5);
//测试一下前序遍历
binaryTree.preOrder();
System.out.println("+++++++++++");
/*//测试一下后续遍历
binaryTree.infixOrder();
System.out.println("+++++++++++");
//后续遍历
binaryTree.postOrder();
System.out.println(binaryTree.infixOrderSearch(3));
System.out.println(binaryTree.postOrderSearch(3));
System.out.println(binaryTree.preOrderSearch(3));*/
System.out.println("删除后");
binaryTree.delNode(3);
binaryTree.preOrder();
}
}
//定义一个树的类
class BinaryTree {
//首先,这个树里面最重要的是根节点
private HeroNode root;
public void setRoot(HeroNode heroNode) {
this.root = heroNode;
}
//删除节点
public void delNode(int no) {
//首先要判断是否为空,然后才能进行后面的操作,不然不行
if (root != null) {
//要判断root是否为要删除的节点,一定要先做
if (root.getNo() == no) {
root = null;//立刻就删除
} else {
//否则直接调用链表类的方法删除
root.deleteNode(no);
}
} else {
System.out.println("树为空,不能删除");
}
}
}
//先创建节点
class HeroNode {
private int no;
private String name;
private HeroNode left;//左边节点,默认为null
private HeroNode right;//右边节点,默认为null
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'';
}
public void setNo(int no) {
this.no = no;
}
public void setName(String name) {
this.name = name;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public void setRight(HeroNode right) {
this.right = right;
}
public int getNo() {
return no;
}
/**
* 首先就是规定
*
* 1、如果删除的节点是叶子节点,则删除该节点
* 2、如果删除的节点是子树,则删除该树
*/
public void deleteNode(int no) {
//1、先判断是否为左子节点,并且严谨一点要判断它是否为空
if (this.left != null && this.left.no == no) {
this.left = null;
return;
}
//2、如果当前节点的右子树不为空,并且右子节点,就是要删除节点,就将this.right = null,并且返回(结束递归)
if (this.right != null && this.right.no == no) {
this.right = null;
return;
}
//3、向左子树递归查找删除,1和2都没有找到的的话,则要向左子树递归删除,
if (this.left != null) {
this.left.deleteNode(no);
//暂时不要return,否则可能有问题
}
//4、向由子树递归查找删除,则应该想右子树进行递归删除
if (this.right != null) {
this.right.deleteNode(no);
}
}
}