为什么需要树这种数据结构?
数组存储方式的分析:
-
优点:通过下标方式访问元素,速度快,对于有序数组,还可使用二分查找来提高检索速度。
-
缺点:如果要检索某个具体的对象值,或者插入(按顺序插入)会整体移动,效率低。
链式存储方式的分析:
-
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,删除效率也很好)。 缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)
总结:
-
数组存储方式:查找速度和修改速度快,而插入元素和删除元素慢
-
链式存储方式:插入元素和删除元素快,而查找速度和修改元素慢
树存储方式的分析:
能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
树的示意图:
树的常用术语(结合示意图理解):
1)节点:每一个圆圈就是一个节点
2)根节点:最上层的A就是根节点
3)父节点:A是B、C的父节点,B是D、E的父节点,以此类推
4)子节点:举例:B、C是A的子节点
5)叶子节点 (没有子节点的节点)
6)节点的权(节点值)
7)路径(从root节点找到该节点的路线):比如从A到H节点要经过A、B、D、E,这个就是A到E的路径。
8)层
9)子树:B下面还有子节点,构成了一棵树,这个B就是A的子树
10)树的高度(最大层数) 上图的树的高度是4层。
11)森林 :多颗子树构成森林
二叉树的概念:
-
树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
-
二叉树的子节点分为左节点和右节点。
如图:
-
如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。
-
如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树
二叉树遍历的说明
-
前序遍历:先输出父节点,然后再遍历左子树,最后遍历右子树。
-
中序遍历:先遍历左子树,然后再输出父节点,最后遍历右子树。
-
后序遍历:先遍历左子树,然后再遍历右子树,最后输出父节点。
看输出父节点的顺序,就确定是前序,中序还是后序。
代码:
package com.lzh.binaryTree;
/**
* 二叉树
*/
public class BInaryTreeDemo {
public static void main(String[] args) {
//创建一个二叉树对象
BinaryTree binaryTree = new BinaryTree();
//创建节点
HeroNode root = new HeroNode(1,"卡莎");//作为根节点
HeroNode node1 = new HeroNode(2,"锐雯");
HeroNode node2 = new HeroNode(3,"薇恩");
HeroNode node3 = new HeroNode(4,"金克斯");
//把子节点加入到根节点中
root.setLeft(node1);
root.setRight(node2);
node2.setRight(node3);
//把根节点放到二叉树中
binaryTree.setRoot(root);
//前序遍历
System.out.println("前序遍历");//1,2,3,4
binaryTree.preErgodic();
//中序遍历
System.out.println("中序遍历");//2,1,3,4
binaryTree.infixErgodic();
//后序遍历
System.out.println("后序遍历");//2,4,3,1
binaryTree.lastErgodic();
}
}
//创建一棵二叉树
class BinaryTree{
//封装一个根节点,有了根节点之后才能调用下面的遍历方法
private HeroNode root;//默认为null
//创建一个set方法,用来联系根节点和子节点的关系
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
public void preErgodic(){
//判断这个根节点是否为空,为空就代表没有子节点,是一棵空的二叉树
if (this.root != null){
this.root.preErgodic();
}else{
System.out.println("二叉树为空,不能进行前序遍历");
}
}
//中序遍历
public void infixErgodic(){
if (this.root != null){
this.root.infixErgodic();
}else{
System.out.println("二叉树为空,不能进行中序遍历");
}
}
//后序遍历
public void lastErgodic(){
if (this.root != null){
this.root.lastErgodic();
}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;
}
//get/set方法
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
//重写toString方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
//前序遍历(递归方法遍历)
public void preErgodic(){
//先输出父节点
System.out.println(this);
//再遍历左节点,如果不为null,就递归遍历
if (this.left != null){
//让父节点的左子节点调用自身
this.left.preErgodic();
}
//再遍历右节点,如果不为null,就递归遍历
if (this.right != null){
//让父节点的右子节点调用自身
this.right.preErgodic();
}
}
//中序遍历(递归方法遍历)
public void infixErgodic(){
//先遍历左节点
if (this.left != null){
this.left.infixErgodic();
}
//然后输出父节点
System.out.println(this);
//再遍历右节点,如果不为null,就递归遍历
if (this.right != null){
this.right.infixErgodic();
}
}
//后序遍历(递归方法遍历)
public void lastErgodic(){
//先遍历左子节点
if (this.left != null){
this.left.lastErgodic();
}
//再递归遍历右子节点
if (this.right != null){
this.right.lastErgodic();
}
//最后输出父节点
System.out.println(this);
}
}
二叉树根据id查找节点:
前序遍历查找:
思路:前提条件:传过来一个要查找的id
-
先判断根节点的id是否和要查找的id相等,如果相等的话,就把根节点返回
-
如果根节点不等于要查找的id,我们就向根节点的左边进行查找,如果根节点的左子节点也不是我们要查找的节点,就把这个左子节点当做父节点,往这个左子节点的下面(子节点)进行递归查找。
-
如果根节点的左子节点以及左子节点下面的子节点也没有找到的话,就往根节点的右子节点进行查找,当这个右子节点也不是我们要查找的节点时,就把这个右子节点当做父节点继续往下进行查找。
查找顺序:父节点=》左子节点=》右子节点
中序遍历查找 :
思路:前提条件:传过来一个要查找的id
-
先从根节点的左子节点进行查找,如果根节点的左子节使我们要查找的节点,就返回这个节点
-
如果左子节点以及左子节点下的节点没有找到,我们就判断根节点是否是我们要查找的节点,如果是,就把这个根节点给返回。
-
如果根节点不是我们要找的节点,我们就查找根节点的右子节点,如果这个右子节点不是我们要查找的节点,我们就继续往右子节点的下面进行递归查找(还是按照这个顺序来)。
查找顺序:根节点的左子节点=》根节点=》右子节点
后序遍历查找:
思路:前提条件:传过来一个要查找的id
-
先从根节点的左子节点进行查找,如果根节点的左子节使我们要查找的节点,就返回这个节点
-
如果左子节点以及左子节点下的节点没有找到,我们就判断根节点的右子节点是否是我们要查找的节点,如果是,就把这个节点给返回。
-
如果根节点的右子节点不是我们要找的节点,我们就查找根节点,如果是就返回这个节点
查找顺序:根节点的左子节点=》根节点的右子节点=》根节点
代码:
package com.lzh.binaryTree;
/**
* 二叉树
*/
public class BInaryTreeDemo {
public static void main(String[] args) {
//创建一个二叉树对象
BinaryTree binaryTree = new BinaryTree();
//创建节点
HeroNode root = new HeroNode(1,"卡莎");//作为根节点
HeroNode node1 = new HeroNode(2,"锐雯");
HeroNode node2 = new HeroNode(3,"薇恩");
HeroNode node3 = new HeroNode(4,"金克斯");
//把子节点加入到根节点中
root.setLeft(node1);
root.setRight(node2);
node2.setRight(node3);
//把根节点放到二叉树中
binaryTree.setRoot(root);
//前序遍历
//System.out.println("前序遍历");//1,2,3,4
// binaryTree.preErgodic();
//中序遍历
//System.out.println("中序遍历");//2,1,3,4
// binaryTree.infixErgodic();
//后序遍历
//System.out.println("后序遍历");//2,4,3,1
//binaryTree.lastErgodic();
// System.out.println("前序通过id查找");
// HeroNode resNode = binaryTree.preErgodic(4);
//System.out.println("中序通过id查找");
// HeroNode resNode = binaryTree.infixErgodic(3);
System.out.println("后序通过id查找");
HeroNode resNode = binaryTree.lastErgodic(4);
if (resNode != null){
System.out.println("要查找的信息为:"+resNode);
}else{
System.out.println("没有找到该节点的信息");
}
}
}
//创建一棵二叉树
class BinaryTree{
//封装一个根节点,有了根节点之后才能调用下面的遍历方法
private HeroNode root;//默认为null
//创建一个set方法,用来联系根节点和子节点的关系
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
public void preErgodic(){
//判断这个根节点是否为空,为空就代表没有子节点,是一棵空的二叉树
if (this.root != null){
this.root.preErgodic();
}else{
System.out.println("二叉树为空,不能进行前序遍历");
}
}
//中序遍历
public void infixErgodic(){
if (this.root != null){
this.root.infixErgodic();
}else{
System.out.println("二叉树为空,不能进行中序遍历");
}
}
//后序遍历
public void lastErgodic(){
if (this.root != null){
this.root.lastErgodic();
}else{
System.out.println("二叉树为空,不能进行后序遍历");
}
}
//前序通过id查找
public HeroNode preErgodic(int no) {
if(root != null) {
return root.preErgodic(no);
} else {
return null;
}
}
//中序通过id查找
public HeroNode infixErgodic(int no) {
if(root != null) {
return root.infixErgodic(no);
} else {
return null;
}
}
//后序通过id查找
public HeroNode lastErgodic(int no){
if (root != null){
return root.lastErgodic(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;
}
//get/set方法
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
//重写toString方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
//前序遍历(递归方法遍历)
public void preErgodic(){
//先输出父节点
System.out.println(this);
//再遍历左节点,如果不为null,就递归遍历
if (this.left != null){
//让父节点的左子节点调用自身
this.left.preErgodic();
}
//再遍历右节点,如果不为null,就递归遍历
if (this.right != null){
//让父节点的右子节点调用自身
this.right.preErgodic();
}
}
//中序遍历(递归方法遍历)
public void infixErgodic(){
//先遍历左节点
if (this.left != null){
this.left.infixErgodic();
}
//然后输出父节点
System.out.println(this);
//再遍历右节点,如果不为null,就递归遍历
if (this.right != null){
this.right.infixErgodic();
}
}
//后序遍历(递归方法遍历)
public void lastErgodic(){
//先遍历左子节点
if (this.left != null){
this.left.lastErgodic();
}
//再递归遍历右子节点
if (this.right != null){
this.right.lastErgodic();
}
//最后输出父节点
System.out.println(this);
}
//前序通过id查找
public HeroNode preErgodic(int no){
//先从根节点开始
if (this.no == no){
//如果根节点是我们要找的节点,就把根节点返回
return this;
}
//创建一个变量
HeroNode resNode = null;
//如果根节点没找到,就递归找根节点的左子节点
if (this.left != null){
resNode = this.left.preErgodic(no);
}
//如果resNode不为空,就代表我们找到了要查找的节点
if (resNode != null){
return resNode;
}
//如果左子节点也没找到,就递归查找根节点的右子节点
if (this.right != null){
resNode = this.right.preErgodic(no);
}
//如果没找到的话就返回null
return resNode;
}
//中序通过id查找
public HeroNode infixErgodic(int no){
HeroNode resNode = null;
//先从根节点的左子节点开始递归查找
if (this.left != null){
resNode = this.left.infixErgodic(no);
}
//判断resNode是否为空,如果不为空代表找到
if (resNode != null){
return resNode;
}
//如果为空代表没有找到,然后开始从当前节点比较,如果id相等就返回
if (this.no == no){
return this;
}
//如果当前节点也没找到的话,就从当前节点的右边开始递归查找
if (this.right != null){
resNode = this.right.infixErgodic(no);
}
//如果没找到的话就返回null
return resNode;
}
//后序通过id查找
public HeroNode lastErgodic(int no){
HeroNode resNode = null;
//先从根节点的左子节点开始递归查找
if (this.left != null){
resNode = this.left.lastErgodic(no);
}
//判断resNode是否为空,如果不为空代表找到
if (resNode != null){
return resNode;
}
//如果当前节点也没找到的话,就从当前节点的右边开始递归查找
if (this.right != null){
resNode = this.right.lastErgodic(no);
}
//判断resNode是否为空,如果不为空代表找到
if(resNode != null) {
return resNode;
}
System.out.println("进入后序查找");
//如果为空代表没有找到,然后开始从当前节点比较,如果id相等就返回
if (this.no == no){
return this;
}
//如果没找到的话就返回null
return resNode;
}
}
二叉树-删除节点:
因为我们这个二叉树是一个普通的二叉树,没有什么规则,所以我们自己定一个规则,来做删除操作,后面有序二叉树就是有自己的规则了
1)如果删除的节点是叶子节点,则删除该节点
2)如果删除的节点是非叶子节点,则删除该子树.
3)测试,删除掉 5号叶子节点 和 3号子树.
思路:
因为我们这个树是单向的,所以不能自我删除,删除节点的话要判断当前节点的下一个节点是否为要删除的节点,而不能去判断当前这个结点是不是需要删除结点.
1.判断当前节点的左子节点是否是要删除的节点,如果是,就this.left = null,并返回(递归删除结束)
2.判断当前节点的右子节点是否是要删除的节点,如果是,就this.right = null,并返回(递归删除结束)
3.如果上面两步没有删除节点的话,就从左子树开始进行递归删除
4.如果第3步也没删除,就从右子树开始进行递归删除
整个操作都是从根节点开始进行
代码:
package com.lzh.binaryTree;
/**
* 二叉树
*/
public class BInaryTreeDemo {
public static void main(String[] args) {
//创建一个二叉树对象
BinaryTree binaryTree = new BinaryTree();
//创建节点
HeroNode root = new HeroNode(1,"卡莎");//作为根节点
HeroNode node1 = new HeroNode(2,"锐雯");
HeroNode node2 = new HeroNode(3,"薇恩");
HeroNode node3 = new HeroNode(4,"金克斯");
HeroNode node4 = new HeroNode(5,"卢仙");
//把子节点加入到根节点中
root.setLeft(node1);
root.setRight(node2);
node2.setLeft(node4);
node2.setRight(node3);
//把根节点放到二叉树中
binaryTree.setRoot(root);
//前序遍历
//System.out.println("前序遍历");//1,2,3,4
// binaryTree.preErgodic();
//中序遍历
//System.out.println("中序遍历");//2,1,3,4
// binaryTree.infixErgodic();
//后序遍历
//System.out.println("后序遍历");//2,4,3,1
//binaryTree.lastErgodic();
// System.out.println("前序通过id查找");
// HeroNode resNode = binaryTree.preErgodic(4);
//System.out.println("中序通过id查找");
// HeroNode resNode = binaryTree.infixErgodic(3);
System.out.println("后序通过id查找");
HeroNode resNode = binaryTree.lastErgodic(4);
if (resNode != null){
System.out.println("要查找的信息为:"+resNode);
}else{
System.out.println("没有找到该节点的信息");
}
//递归删除
System.out.println("删除前的二叉树:");
binaryTree.preErgodic();
//进行删除
binaryTree.delNode(5);
System.out.println("删除后的二叉树");
binaryTree.preErgodic();
}
}
//创建一棵二叉树
class BinaryTree{
//封装一个根节点,有了根节点之后才能调用下面的遍历方法
private HeroNode root;//默认为null
//创建一个set方法,用来联系根节点和子节点的关系
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
public void preErgodic(){
//判断这个根节点是否为空,为空就代表没有子节点,是一棵空的二叉树
if (this.root != null){
this.root.preErgodic();
}else{
System.out.println("二叉树为空,不能进行前序遍历");
}
}
//中序遍历
public void infixErgodic(){
if (this.root != null){
this.root.infixErgodic();
}else{
System.out.println("二叉树为空,不能进行中序遍历");
}
}
//后序遍历
public void lastErgodic(){
if (this.root != null){
this.root.lastErgodic();
}else{
System.out.println("二叉树为空,不能进行后序遍历");
}
}
//前序通过id查找
public HeroNode preErgodic(int no) {
if(root != null) {
return root.preErgodic(no);
} else {
return null;
}
}
//中序通过id查找
public HeroNode infixErgodic(int no) {
if(root != null) {
return root.infixErgodic(no);
} else {
return null;
}
}
//后序通过id查找
public HeroNode lastErgodic(int no){
if (root != null){
return root.lastErgodic(no);
}else{
return null;
}
}
//递归删除节点
public void delNode(int no){
//判断根节点是否为空
if (root != null){
//如果只有一个节点,就判断这个根节点是否是我们要删除的节点
if (root.getNo() == no){
//是的话就把根节点置空
root = null;
}else{
//不是的话就开始递归删除
root.delNode(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;
}
//get/set方法
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
//重写toString方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
//前序遍历(递归方法遍历)
public void preErgodic(){
//先输出父节点
System.out.println(this);
//再遍历左节点,如果不为null,就递归遍历
if (this.left != null){
//让父节点的左子节点调用自身
this.left.preErgodic();
}
//再遍历右节点,如果不为null,就递归遍历
if (this.right != null){
//让父节点的右子节点调用自身
this.right.preErgodic();
}
}
//中序遍历(递归方法遍历)
public void infixErgodic(){
//先遍历左节点
if (this.left != null){
this.left.infixErgodic();
}
//然后输出父节点
System.out.println(this);
//再遍历右节点,如果不为null,就递归遍历
if (this.right != null){
this.right.infixErgodic();
}
}
//后序遍历(递归方法遍历)
public void lastErgodic(){
//先遍历左子节点
if (this.left != null){
this.left.lastErgodic();
}
//再递归遍历右子节点
if (this.right != null){
this.right.lastErgodic();
}
//最后输出父节点
System.out.println(this);
}
//前序通过id查找
public HeroNode preErgodic(int no){
//先从根节点开始
if (this.no == no){
//如果根节点是我们要找的节点,就把根节点返回
return this;
}
//创建一个变量
HeroNode resNode = null;
//如果根节点没找到,就递归找根节点的左子节点
if (this.left != null){
resNode = this.left.preErgodic(no);
}
//如果resNode不为空,就代表我们找到了要查找的节点
if (resNode != null){
return resNode;
}
//如果左子节点也没找到,就递归查找根节点的右子节点
if (this.right != null){
resNode = this.right.preErgodic(no);
}
//如果没找到的话就返回null
return resNode;
}
//中序通过id查找
public HeroNode infixErgodic(int no){
HeroNode resNode = null;
//先从根节点的左子节点开始递归查找
if (this.left != null){
resNode = this.left.infixErgodic(no);
}
//判断resNode是否为空,如果不为空代表找到
if (resNode != null){
return resNode;
}
//如果为空代表没有找到,然后开始从当前节点比较,如果id相等就返回
if (this.no == no){
return this;
}
//如果当前节点也没找到的话,就从当前节点的右边开始递归查找
if (this.right != null){
resNode = this.right.infixErgodic(no);
}
//如果没找到的话就返回null
return resNode;
}
//后序通过id查找
public HeroNode lastErgodic(int no){
HeroNode resNode = null;
//先从根节点的左子节点开始递归查找
if (this.left != null){
resNode = this.left.lastErgodic(no);
}
//判断resNode是否为空,如果不为空代表找到
if (resNode != null){
return resNode;
}
//如果当前节点也没找到的话,就从当前节点的右边开始递归查找
if (this.right != null){
resNode = this.right.lastErgodic(no);
}
//判断resNode是否为空,如果不为空代表找到
if(resNode != null) {
return resNode;
}
System.out.println("进入后序查找");
//如果为空代表没有找到,然后开始从当前节点比较,如果id相等就返回
if (this.no == no){
return this;
}
//如果没找到的话就返回null
return resNode;
}
// 递归删除结点
// //1.如果删除的节点是叶子节点,则删除该节点
// //2.如果删除的节点是非叶子节点,则删除该子树
public void delNode(int no){
/**
* 思路:因为我们这个树是单向的,所以不能自我删除,删除节点的话要判断当前节点的下一个节点是否为要删除的节点,而不能去判断当前这个结点是不是需要删除结点.
* 1.判断当前节点的左子节点是否是要删除的节点,如果是,就this.left = null,并返回(递归删除结束)
* 2.判断当前节点的右子节点是否是要删除的节点,如果是,就this.right = null,并返回(递归删除结束)
* 3.如果上面两步没有删除节点的话,就从左子树开始进行递归删除
* 4.如果第3步也没删除,就从右子树开始进行递归删除
* 整个操作都是从根节点开始进行
*/
//当前节点的左子节点是否是要删除的节点,如果是,就this.left = null,并返回(递归删除结束)
if (this.left != null && this.left.no == no){
this.left = null;
return;
}
//判断当前节点的右子节点是否是要删除的节点,如果是,就this.right = null,并返回(递归删除结束)
if (this.right != null && this.right.no == no){
this.right = null;
return;
}
//如果上面两步没有删除节点的话,就从左子树开始进行递归删除
if (this.left != null){
this.left.delNode(no);
}
//如果上一步也没删除,就从右子树开始进行递归删除
if (this.right != null){
this.right.delNode(no);
}
}
}