树
因为是跟着视频学的所以这个地方也就分为两个部分了
基础部分
为什么需要树这种数据结构
1.先说数组存储的方式吧,我们都知道,数组有很多排序算法,也有常见的查找算法,这使我们操作起来很方便,但是呢,我们发现其实这种方便也仅仅是在于查找,排序那种简单类型的,遇到对象类型的就比较麻烦,而且插入的时候会整体移动,效率比较低。
2.链式存储方式,在一定程度上对数组的存储方式进行了改善,比如插入某个值,我们只需要将插入的节点连接到表中即可,删除的效率也很高,但是他的查询检索效率对应的就低下来了,比如检索某个值,需要从头开始便利
详细的说数组存储方式
我们要想在数组中插入一个数据,我们都知道数组一开始是事先分配好空间的,我们要想在原数组中插入新的数据,需要进行一个数组扩容,这个数组扩容规定每次在底层都需要创建新的数组要将原来的数据拷贝到数组,并插入新的数据,我们常见的那个ArrayList数组底层也是维护了一个Object数组(他也是这种方式扩容(只是方法不同))
我找了下老师关于ArrayList数组底层笔记
- ArrayList中维护了一个Object类型的数组elementData. [debug看源码]
- 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0 (jdk7 是10)
- 如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity.
- 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,香则直接添加元素到合适位置
- 如果使用的是无参构造器,如果第一次添加, 需要扩容的话,则打容elementData为10如果需要再次扩容的话,则扩容elementData为1.5倍。
- 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍
//以ArrayList为例,展示怎么扩容
//2021年1月24日22:04:07
//@author 王
public class Test {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
/**
* Constructs an empty list with an initial capacity of ten.
*/
// public ArrayList() {
// this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};他是一个空数组
// }
/**
* ArrayList底层仍然是数组扩容
*/
/**
* 机制
*
*/
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
}
grow就是他的扩容方法
那我们的链表呢?链表插入操作就很简单了,只需要把上一个元素的指针指向我们待添加的元素就行了,待添加的元素的指针指向原来的下一个
树存储方式
通俗的来讲,他就是既优化了查询,又优化了增删操作
那如果以二叉排序树来存储数据,流程是什么?为什么两者都优化了?
以老师的数组举例,{7,3,10,1,5,9,12},那么二叉排序树的结构就是
查找操作是不是简单了?比7大的直接去右边子树查找,有点折半的意思了吧,比我们最平常的查找是不是快
添加呢?加入添加14,哪找位置就是定位到了12,比12大那么应该插入到12的右子树上,删除呢?删除同样也是这么快速
不言而喻,他的好处我们都能感觉到了
常用术语
至于树的形状,就不在这里说了
里面有这几个名词
1.节点 通俗讲就是每一个数据
2.根节点 就是最上面那个第一层的那个节点
3.父节点 该节点的直接上级节点
4.子节点 该节点的直接下级节点
5.叶子结点 就是没有子节点了,最后一层的节点肯定是叶子节点,也有可能在其它层
6.节点的权 就是节点值
7.路径 从根节点找到目标节点的路线
8.层
9.子树
10.数的高度 最大层数
11.森林 多颗子树构成森林
二叉树
每个节点最多只能有两个子节点的一种形式的树
关于二叉树的一些特点有必要记一下
1.如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n-1,n为层数,则我们称为满二叉树
2.如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
二叉树的遍历
前序遍历:先输出父节点,再遍历子节点
中序遍历:先遍历左子树,在输出父节点,在遍历右子树
后序遍历:先遍历左子树,再便利右子树,最后输出父节点
所以我们可以通过父节点输出的顺序,确定是那种遍历方式
代码
我们需要创建的数是这样的
那么上代码
先是我们的节点代码
//先创建HeroNode节点
class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int no, String name) {
super();
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);//输出父节点
}
}
我们的节点写出来之后就是我们的树跟节点怎么关联呢?——》定义我们的二叉树
//定义一个BinnaryTree二叉树
class BinnaryTreeDemo{
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
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("二叉树为空,无法遍历");
}
}
}
这样我们就可以通过setRoot方法跟我们的节点关联起来了,我们的树就可以创建了
测试代码
package 树;
//二叉树的前序、中序、后序遍历
//2021年1月25日22:57:42
//@author 王
public class BinnaryTree {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一颗二叉树
BinnaryTreeDemo binnaryTreeDemo = new BinnaryTreeDemo();
//创建节点
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);
binnaryTreeDemo.setRoot(root);
//测试
System.out.println("前序遍历");
binnaryTreeDemo.preOrder();
System.out.println("中序遍历");
binnaryTreeDemo.infixOrder();
System.out.println("后序遍历");
binnaryTreeDemo.postOrder();
}
}
这样最简单的前中后序遍历就出来了,我们看看效果
前序遍历
HeroNode [no=1, name=小王]
HeroNode [no=2, name=小胡]
HeroNode [no=3, name=小黄]
HeroNode [no=4, name=老肥]
中序遍历
HeroNode [no=2, name=小胡]
HeroNode [no=1, name=小王]
HeroNode [no=3, name=小黄]
HeroNode [no=4, name=老肥]
后序遍历
HeroNode [no=2, name=小胡]
HeroNode [no=4, name=老肥]
HeroNode [no=3, name=小黄]
HeroNode [no=1, name=小王]
后续改进下一篇博客笔记在写出来,先把全部代码贴一遍
package 树;
//二叉树的前序、中序、后序遍历
//2021年1月25日22:57:42
//@author 王
public class BinnaryTree {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一颗二叉树
BinnaryTreeDemo binnaryTreeDemo = new BinnaryTreeDemo();
//创建节点
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);
binnaryTreeDemo.setRoot(root);
//测试
System.out.println("前序遍历");
binnaryTreeDemo.preOrder();
System.out.println("中序遍历");
binnaryTreeDemo.infixOrder();
System.out.println("后序遍历");
binnaryTreeDemo.postOrder();
}
}
//定义一个BinnaryTree二叉树
class BinnaryTreeDemo{
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
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("二叉树为空,无法遍历");
}
}
}
//先创建HeroNode节点
class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int no, String name) {
super();
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);//输出父节点
}
}