前面咱们学了不一样的数据结构,今天学习的是一种特别的数据结构:树
node
首先咱们思考一下:什么是树呢?为何咱们须要学习这种数据结构呢?
segmentfault
咱们对比以前学习的数据结构,分析看看以前的数据结构有什么特色又有什么缺陷数组
1、数组存储方式的分析数据结构
优势:经过下标方式访问元素
,速度快。对于有序数组
,可使用二分查找
提升检索速度。ide
缺点:若是要操做具体插入值
,那么会总体移动(按必定顺序),效率较低post
假如我当前有数组arr {1,3,5,8,10}
,若此时插入数据:6
那么能放的进去吗?学习
其实是不能
的,由于数组是事先分配空间
的,指说原建立好空间长度就不能动态增加
,可是数组在动态添加数据
的时候,底层有一个动做:数组扩容
。测试
那么是如何扩容的呢?建立新的数组,并将数据拷贝,以及插入数据后移
优化
那么这时会有小伙伴提出:咱们使用集合ArrayList不是能够动态增加吗?
this
其实咱们观察集合ArrayList,发现也维护了数组扩容,只是策略不一样。
那么咱们一块儿看看ArrayList 源码
private static final Object [] DEFAULTCAPACITY_ EMPTY_ ELEMENTDATA = {}; //ArrayList的构造器 public ArrayList() { this.elementData = DEFAULTCAPACITY_ EMPTY_ ELEMENTDATA ; }
咱们发现ArrayList无参构造器,上来时将空数值给到elementData数组。
那么elementData是什么?
transient 0bject [] elementData;
实际上是一个对象Object数组
,也就是说ArrayList维护的0bject [] elementData数组
在ArrayList容量不够的时候,有方法grow()按照不一样的策略进行扩容,但仍然是一个数组扩容
private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity - oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copy0f(elementData, newCapacity); }
Object类型的数组elementData
.无参构造器
,则初始elementData容量为0 (jdk7是10)
指定容量capacity的构造器
,则初始elementData 容量为capacity
.添加元素
时: 先判断是否须要扩容
,若是须要扩容
,则调用grow方法,不然直接添加元素到合适位置
无参构造器
,若是第一次添加
,须要扩容
的话,则扩容elementData为10
,若是须要再次扩容的话
,则扩容elementData为1.5倍
。指定容量capacity的构造器
,若是还须要扩容,则直接扩容elementData为1.5倍。咱们发现ArrayList为了解决扩容,按照一种策略进行的,仍是会总体移动的,效率比较低,因而咱们看看链式存储方式能不能更好解决问题
2、链式存储方式的分析
优势:在必定程度上对数组存储方式有优化
(好比:插入数值
节点,只须要将插入节点,连接到链表中
便可,删除效率也很好)。
缺点:在进行检索时,效率仍然较低(好比:检索某个值
,须要从头节点开始
遍历)
咱们发现数组与链式都有各自的优势与缺点,那么接下来介绍新的数据结构:树
有什么不一样呢?
在树的家族中,有一种高频使用
的一种树结构
:二叉树
。
在二叉树
中,每一个节点最多有两个分支
,即每一个节点最多有两个节点,分别称为左节点与右节点
。
在二叉树中,还有两个特殊的类型:满二叉树与彻底二叉树
。
满二叉树:除了叶子节点外,全部节点都有两个节点
。
彻底二叉树:除了最后一层之外,其余层节点个数都达到最大,而且最后一层的叶子节点向左排列
这和存储二叉树的两种存储方法:链式存储法与数组顺序法
。有关了
基于指针的链式存储法
,也就是像链表
同样,每一个节点有三个字段
。一个存储数据,两外两个分别存储指向左右节点的指针
,以下图所示
基于数组的顺序存储法
,就是按照规律把节点存放在数组里
,以下图所示。为了方便按照规律计算,把起始数据放在下标为一的位置上
如果非彻底二叉树则会浪费大量的数组存储空间,以下图所示
以二叉树
为例介绍树的操做
一对一
"的关系,即前面的数据只跟下面的一个数据产生链接关系
。如链表、栈、队列等一对多
"的关系,即前面的父节点跟若干个子节点产生了链接关系
与以前的数据结构相比,遍历一个树
。
有很是经典的三种方法,分别是前序遍历、中序遍历、后序遍历
示例一:使用前序、中序、后序对下面二叉树进行遍历
咱们根据图片定义英雄节点HeroNode 信息
//建立英雄节点HeroNode 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; } 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; } @Override public String toString() { return "HeroNode [no =" + no +", name =" + name +"]"; } /** * 前序遍历方式 * 前序是先输出父节点,再遍历左子树和右子树 */ public void preOrder(){ //先输出父节点 System.out.println(this); //递归向左节点进行前序遍历 if(this.left!=null){ this.left.preOrder(); } //递归向右节点进行前序遍历 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); } }
咱们建立一颗二叉树 BinaryTree 信息
//定义二叉树 class BinaryTree{ private HeroNode root; //根节点 public void setRoot(HeroNode root) { this.root = root; } //root根节点前序遍历的方法 public void preOrder(){ if(this.root!=null){ this.root.preOrder(); }else{ System.out.println("二叉树为空,没法遍历"); } } //root根节点中序遍历的方法 public void infixOrder(){ if(this.root!=null){ this.root.infixOrder(); }else{ System.out.println("二叉树为空,没法遍历"); } } //root根节点后序遍历的方法 public void postOrder(){ if(this.root!=null){ this.root.postOrder(); }else{ System.out.println("二叉树为空,没法遍历"); } } }
如今让咱们如图所示建立二叉树与英雄节点来测试遍历看看
public class BinaryTreeDemo { public static void main(String [] agrs){ //建立二叉树 BinaryTree tree =new BinaryTree(); //建立英雄节点 HeroNode root = new HeroNode(1,"松江"); HeroNode node2 =new HeroNode(2,"吴用"); HeroNode node3 =new HeroNode(3,"卢俊"); HeroNode node4 =new HeroNode(4,"林冲"); //手动建立二叉树依赖 root.setLeft(node2); root.setRight(node3); node3.setRight(node4); //将root 节点给到二叉树 tree.setRoot(root); } }
咱们测试前序遍历
,看看是否结果是[宋江-->吴用-->卢俊-->林冲]
//前序遍历 tree.preOrder(); HeroNode [no =1, name =松江] HeroNode [no =2, name =吴用] HeroNode [no =3, name =卢俊] HeroNode [no =4, name =林冲]
咱们测试中序遍历
,看看是否结果是[吴用-->松江-->卢俊-->林冲]
//中序遍历 tree.infixOrder(); HeroNode [no =2, name =吴用] HeroNode [no =1, name =松江] HeroNode [no =3, name =卢俊] HeroNode [no =4, name =林冲]
咱们测试后序遍历
,看看是否结果是[吴用-->林冲-->卢俊-->松江]
//后序遍历 tree.postOrder(); HeroNode [no =2, name =吴用] HeroNode [no =4, name =林冲] HeroNode [no =3, name =卢俊] HeroNode [no =1, name =松江]
1.上图的3号节点"卢俊",添加左节点[5,关胜]
2.使用前序、中序、后序 写出各自输出顺序是啥?
示例二:使用前序、中序、后序不一样遍历对下面二叉树进行查找
接下来咱们在分别在HeroNode、BinaryTree添加代码
//添加前序、中序、后序查找代码 class HeroNode { //省略以前序、中序、后序遍历代码 /** * @param no 查找no * @return 若是找到返回node,没有返回null */ public HeroNode preOrderSearch(int no){ System.out.println("进入前序遍历查找~~~~"); //比较当前节点看看是否是 if(this.no == no){ return this; } //1.判断当前节点的左节点是否为空,不为空递归前序查找,找到符合要求的节点则返回 HeroNode resnode = null; if(this.left!=null){ resnode = this.left.preOrderSearch(no); } //说明左节点找到了,相等 if(resnode != null){ return resnode; } //不相等则,判断当前节点的右节点是否为空,不为空递归前序查找 if(this.right != null){ resnode = this.right.preOrderSearch(no); } return resnode; } /** * @param no 查找no * @return 若是找到返回node,没有返回null */ public HeroNode infixOrderSearch(int no){ //1.判断当前节点的左节点是否为空,不为空递归中序查找,找到符合要求的节点则返回 HeroNode resnode = null; if(this.left!=null){ resnode = this.left.infixOrderSearch(no); } //说明符合要求的节点找到了,相等 if(resnode != null){ return resnode; } System.out.println("进入中序遍历查找~~~~"); //没有则和当前节点进行比较,知足则返回 if(this.no == no){ return this; } //不相等则,判断当前节点的右节点是否为空,不为空递归前序查找 if(this.right != null){ resnode = this.right.infixOrderSearch(no); } return resnode; } /** * @param no 查找no * @return 若是找到返回node,没有返回null */ public HeroNode postOrderSearch(int no){ //1.判断当前节点的左节点是否为空,不为空递归后序查找,找到符合要求的节点则返回 HeroNode resnode = null; if(this.left!=null){ resnode = this.left.postOrderSearch(no); } //说明符合要求的节点找到了,相等 if(resnode != null){ return resnode; } //不相等则,判断当前节点的右节点是否为空,不为空右递归后序查找 if(this.right != null){ resnode = this.right.postOrderSearch(no); } //说明符合要求的节点找到了,相等 if(resnode != null){ return resnode; } System.out.println("进入后序遍历查找~~~~"); //没有则和当前节点进行比较,知足则返回 if(this.no == no){ return this; } return resnode; } }
//添加前序、中序、后序查找代码 class BinaryTree{ //省略以前序、中序、后序遍历代码 //root节点前序查找方法 public HeroNode preOrderSearch(int no){ if(this.root != null){ return this.root.preOrderSearch(no); }else{ return null; } } //root节点中序查找方法 public HeroNode infixOrderSearch(int no){ if(this.root != null){ return this.root.infixOrderSearch(no); }else{ return null; } } //root节点后序查找方法 public HeroNode postOrderSearch(int no){ if(this.root != null){ return this.root.postOrderSearch(no); }else{ return null; } } }
[舒适提示]:小伙伴必定要添加好关胜英雄数据
并关联起树的关系
哦
//建立英雄节点 HeroNode node5 =new HeroNode(5,"关胜"); //手动关联二叉树依赖关系 node3.setLeft(node5);
使用前序遍历查找
测试[编号:5 关胜]
看看,看看是否找到
并是否四次找到
System.out.println("==========================使用前序遍历查找方式"); HeroNode resNode = tree.preOrderSearch(5); if (resNode != null) { System. out.printf("找到了,信息为no=%d name=%s", resNode.getNo(), resNode. getName()); } else { System.out. printf("没有找到no = %d的英雄",5); } 运行输出结果以下: ==========================使用前序遍历查找方式 进入前序遍历查找~~~~ 进入前序遍历查找~~~~ 进入前序遍历查找~~~~ 进入前序遍历查找~~~~ 找到了,信息为no=5 name=关胜
使用中序遍历查找
测试[编号:5 关胜]
看看,看看是否找到
并是否三次找到
System.out.println("==========================使用中序遍历查找方式~~~"); HeroNode resNode = tree.infixOrderSearch(5); if (resNode != null) { System. out.printf("找到了,信息为no=%d name=%s", resNode.getNo(), resNode. getName()); } else { System.out. printf("没有找到no = %d的英雄",5); } 运行输出结果以下: ==========================使用中序遍历查找方式~~~ 进入中序遍历查找~~~~ 进入中序遍历查找~~~~ 进入中序遍历查找~~~~ 找到了,信息为no=5 name=关胜
使用后序遍历查找
测试[编号:5 关胜]
看看,看看是否找到
并是否二次找到
System.out.println("==========================使用后序遍历查找方式~~~"); HeroNode resNode = tree.postOrderSearch(5); if (resNode != null) { System. out.printf("找到了,信息为no=%d name=%s", resNode.getNo(), resNode. getName()); } else { System.out. printf("没有找到no = %d的英雄",5); } 运行输出结果以下: ==========================使用后序遍历查找方式~~~ 进入后序遍历查找~~~~ 进入后序遍历查找~~~~ 找到了,信息为no=5 name=关胜
由于目前的二叉树:暂时是没有规则的
,后边深刻时再解决怎么把左节点或者右节点提高上去的问题。
示例三:
删除
的节点是叶子节
点,则删除该节点
删除
的节点是非叶子节
点,则删除该子树
.删除
叶子节点五号
和子树三号
.接下来咱们在分别在HeroNode、BinaryTree添加代码
//添加删除节点代码 class HeroNode { //省略以前序、中序、后序遍历代码 //省略以前序、中序、后序查找代码代码 //递归删除结点 //1.若是删除的节点是叶子节点,则删除该节点 //2.若是删除的节点是非叶子节点,则删除该子树 public void delHerNode(int no){ //思路 // 由于咱们的二叉树是链表单向的,因此删除目标节点时不能直接判断是否删除该节点。 // 1.若是当前节点左子节点不为空,而且左子节点是须要删除的节点就将this.left = null,并结束返回 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.若是当前节点没有删除的节点,则判断左节点是否为空,进行左递归继续删除 if(this.left != null){ this.left.delHerNode(no); } // 4.若是左节点左递归没有删除的节点,则判断右节点是否为空,进行右递归继续删除 if(this.right != null){ this.right.delHerNode(no); } } }
//添加删除节点代码 class BinaryTree{ //省略以前序、中序、后序遍历代码 //省略以前序、中序、后序查找代码代码 public void delHerNode(int no){ //若是树自己为空,只有一个root节点则等价于二叉树置空 if(root !=null){ //若是只有一个root结点,这里当即判断root是否是就是要删除结点 if(root.getNo() == no){ root = null; }else{ root.delHerNode(no); } }else{ System.out.println("空树!不能删除"); } } }
使用删除节点
测试[编号:5 关胜]
看看,看看是否成功
System.out.println("==============前序遍历显示删除前数据"); tree.preOrder(); System.out.println("==========================================删除叶子节点五号:关胜"); tree.delHerNode(5); System.out.println("==============前序遍历显示删除后数据"); tree.preOrder(); 运行结果以下: ==============前序遍历显示删除前数据 HeroNode [no =1, name =松江] HeroNode [no =2, name =吴用] HeroNode [no =3, name =卢俊义] HeroNode [no =5, name =关胜] HeroNode [no =4, name =林冲] ==========================================删除叶子节点五号:关胜 ==============前序遍历显示删除后数据 HeroNode [no =1, name =松江] HeroNode [no =2, name =吴用] HeroNode [no =3, name =卢俊义] HeroNode [no =4, name =林冲]
执行删除[叶子节点五号:关胜]
,看看是如何进行的吧!
当执行方法时,先判断当前root 是否为空
,紧接着判断当前root 节点no 是否等于须要删除的节点no
,不知足条件进入delNode方法
delNode方法里,判断当前节点宋江左节点
是不是[五号:关胜]
,但左节点当前为[二号:吴用]
,不知足因而接着判断右节点
但右节点当前为[三号:卢俊义]
,不知足条件判断
因而判断当前左节点是否为空
,不为空则进行左递归查询再次进入delNode方法,那么当前节点为[二号:吴用]
delNode方法里,判断当前节点吴用左节点
是不是[五号:关胜]
,但当前吴用节点左节点为空因而接着判断右节点
但右节点当前为空,不知足条件判断
因而判断当前左节点是否为空,不为空则进行左递归查询再次进入,可是很遗憾,吴用的左边是null,则进行判断右节点
可是吴用右边也是null,则往回溯回到宋江的左递归查询
接着判断当前宋江右节点
是否为空,不为空则进行右递归查询再次进入delNode方法,那么当前节点为[三号:卢俊义]
delNode方法里,判断当前节点吴用左节点是不是[五号:关胜]
,知足条件则卢俊义左节点更改成:null