文章目录
为什么需要树结构:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/021028769fce827dbde702743bb5d877.png)
数组的扩容(Arraylist扩容):
二叉树的基本概念:
二叉树的遍历:
//前序遍历(本-左-右)
public void preorder(){
System.out.println(this);
if(this.left!=null){
this.left.preorder();
}
if(this.right!=null){
this.right.preorder();
}
}
//中序遍历(左-本-右)
public void midorder(){
if(this.left!=null){
this.left.midorder();
}
System.out.println(this);
if(this.right!=null){
this.right.midorder();
}
}
//后序遍历(左-右-本)
public void postorder(){
if(this.left!=null){
this.left.postorder();
}
if(this.right!=null){
this.right.postorder();
}
System.out.println(this);
}
二叉树的查找
//前序查找
public Heronode presearch(int no){
//中间(真正返回的节点)
if(this.no==no){
return this;
}
//左边递归
Heronode resnode=null;
if(this.left!=null){
resnode=this.left.presearch(no);
}
//开始回溯:两种极端情况:1.找到了停止; 2.没有找到继续往下别的查找(右)
if(resnode!=null){//说明找到了
return resnode;
}
//resnode==null,左边递归没找到,右边递归(最后的最后)
if(this.right!=null){
resnode=this.right.presearch(no);
}
//开始回溯:两种极端情况:1.找到了停止; 2.没有找到直接返回null
return resnode;
}
//中序查找
public Heronode midsearch(int no){
//左边递归
Heronode resnode=null;
if(this.left!=null){//左边的子节点已经遍历完了,都没有找到
resnode=this.left.midsearch(no);
}
//开始回溯:两种极端情况:1.找到了停止; 2.没有找到继续往下别的查找(中和右)
if(resnode!=null){//说明找到了
return resnode;
}
//resnode==null,左边递归没找到,中间(真正返回的节点)
if(this.no==no){
return this;
}
//右边递归(最后的最后)
if(this.right!=null){
resnode=this.right.presearch(no);
}
//开始回溯:两种极端情况:1.找到了停止; 2.没有找到直接返回null
return resnode;
}
//后序查找
public Heronode postsearch(int no){
//左边递归
Heronode resnode=null;
if(this.left!=null){//左边的子节点已经遍历完了,都没有找到
resnode=this.left.postsearch(no);
}
//开始回溯:两种极端情况:1.找到了停止; 2.没有找到继续往下别的查找(右和中)
if(resnode!=null){//说明找到了
return resnode;
}
//右边递归(最后的最后)
if(this.right!=null){
resnode=this.right.postsearch(no);
}
//开始回溯:两种极端情况:1.找到了停止; 2.没有找到继续往下别的查找(中)
if(resnode!=null){//说明找到了
return resnode;
}
//resnode==null,左右边递归没找到,中间(真正返回的节点)
if(this.no==no){
return this;
}
return null;//如果内有一个找到
}
二叉树结点的删除
注意:
1.一定是判断是否删除当前节点的子节点,而不是判断节点本身是不是需要删除的节点。(这个方法写在节点类内部)
2.所以我们在二叉树的类中, 一上来要判断判断该树是不是空树------即root是否是空!!!!!
一定是调用当前root节点的删除方法来删除子节点的,所以我们还需要判断删除的节点是不是我们本来的root根节点。
本节点处删除左右的子节点
//在当前的非空节点处根据no来查找要删除的节点并删除
public void deleteno(int no){
if(this.left!=null && this.left.no==no){//本节点的左节点正好是要删除的节点
this.left=null;
return;
}
if(this.right!=null&& this.right.no==no){//本节点的右节点正好是要删除的节点
this.right=null;
return;
}
if(this.left!=null){//继续向左递归,递归结束的条件
this.left.deleteno(no);
}
if(this.right!=null){//继续向右递归
this.right.deleteno(no);
}
}
root根节点出的删除情况判断
//根据根节点来删除要删除的节点
public void deletenode(int no){
//注意根节点的情况需要首先判断,原因根节点没有父节点
if(root!=null){
if(root.getNo()==no){//注意no这个成员变量是private类型的
root=null;
}else{
root.deleteno(no);//调用本节点删除子节点的方法
}
}else{
System.out.println("这是个空树");
}
}
顺序存储二叉树:
二叉树和数组之间的相互转化,抓住“本节点若为n,则它的左节点是2n+1,它的右节点是2n+2”的特点
具体的实现顺序存储二叉树的代码:
线索化二叉树:
*对二叉树进行“—中序—线索化(原来空指针的节点)----”(具体参照中序遍历的代码学习)
//lefttype==0:指向左子树,lefttype==1:指向前驱结点
//righttype==0:指向y右子树,righttype==1:指向后驱结点
private int lefttype;
private int righttype;
//为了实现线索化,需要一个给当前节点的前驱结点的一个指针
//中序遍历的第一个节点的前序节点本身就是空的,所以初始化这个pre节点时为null合理
private HeronodeTr pre=null;//递归时,总是保留前一个节点
//***********对二叉树进行“---中序---线索化----”***************
/**
*
* @param :node就是要线索化的节点(进行左右节点是否为空判断,需不需要线索化)
*/
public void threadedNode(HeronodeTr node){
//判断节点是空,就不需要进行任何的操作
if(node==null){
return;//线索化左子树的递归结束的标志
}
//1.先线索化左子树(递归结束的标志在上面)
threadedNode(node.getLeft());
//2.线索化当前节点
//2.1先处理当前节点的前驱结点
if(node.getLeft()==null){//当前节点的左指针是空的,则指向前驱结点
node.setLeft(pre);
node.setLefttype(1);
}
//2.2处理后继节点
if(pre!=null && pre.getRight()==null){
pre.setRight(node);//让前驱结点的有指针指向当前的节点
pre.setRighttype(1);
}
//每处理一个节点后,让当前节点的下一个节点的前驱结点指向当前节点
pre=node;
//3.线索化右子树
threadedNode(node.getRight());
}
线索化二叉树的遍历:不能使用原来的中序遍历(出现死龟)
//实际上线索化后,本节点中,若lefttype=1的那么它的左节点一定是它的前驱结点,设置lefttype=1说明右结点一定是它的后驱节点!!!!!
//本节点中,若lefttype=1的那么它的左节点一定是它的前驱结点,可以直接输出本节点
// 若righttype=1说明右结点一定是它的后驱节点,可以直接接下来直接输出本节点的右节点
//线索化后,如果当前节点的lefttype=0和lefttype=0说明他完全不是一个空指针的节点,这时它如果是某个节点的后驱节点那么他已经输出了,那么它之后输出的下一个节点,一定是那个“某个节点的前驱结点指向本节点”得一个节点,也就是说需要移动指针来寻找这个“某节点”!!!!
//(某节点的左节点可能是前驱结点,也可能不是—不是就不能输出;右节点可能是前驱结点,也可能不是–不是就不能输出)
//中序线索化的--遍历--
// (某节点的左节点可能是前驱结点,也可能不是---不是就不能输出;
// 右节点可能是前驱结点,也可能不是--不是就不能输出)
public void misorderX(){
//注意根节点不一定是第一个打印的节点
HeronodeTr node=root;
//本节点中,若lefttype=1的那么它的左节点一定是它的前驱结点,可以直接输出本节点
// 若righttype=1说明右结点一定是它的后驱节点,可以直接接下来直接输出本节点的右节点
while(node!=null){
//寻找有前驱结点的那个节点,没有不能输出了(这样设置的原因是,初始化的前驱结点是个空)
while (node.getLefttype()==0){
node=node.getLeft();//终止时的节点就是有前驱结点的那个节点
}
//直接打印输出有前驱结点的那个节点,原因他一定是前驱结点要打印的下一个节点
System.out.println(node);
//是本节点有后驱节点(来自于本节点的右节点)就直接打印这个后驱节点
while(node.getRighttype()==1){
node=node.getRight();
System.out.println(node);
}
//本节点没有后驱节点,就直接下一个(即指向右节点),回去循环直接判断这个节点有没有前驱结点
node=node.getRight();
}
}
树的应用:赫夫曼编树
==规则:==首先将每个元素看成单独的一个简单的二叉树(根节点就是本身),每次挑选出最小的两个根节点来生成一个新的二叉树,然后将新生产的这个二叉树的顶节点作为根节点,继续比较剩下的根节点和这个新生根节点,继续挑选出最小的两个根节点来生成一个新的二叉树。。。。。重复上述的操作,直到数组的元素都被用完!!!!
使用到comparable接口的排序学习
//创建哈夫曼树(返回树的头结点)
public static Node createHuffman(int[] arr){
List<Node> nodes=new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
nodes.add(new Node(arr[i]));
}
Collections.sort(nodes);//列表排序
while(nodes.size()>1){//最后列表只剩一个最终的根节点
//逐一进来的列表排完序的
Node leftnode=nodes.get(0);//左节点权值更小
Node righttnode=nodes.get(1);//左节点权值更小
Node parentnode=new Node(leftnode.value+ righttnode.value);
//这个父节点的左右节点还需要重新生成
parentnode.left=leftnode;
parentnode.right=righttnode;
//更新待排序的列表结点
nodes.remove(leftnode);
nodes.remove(righttnode);
nodes.add(parentnode);
Collections.sort(nodes);//列表重新排序
}
return nodes.get(0);
}
}
class Node implements Comparable<Node>{
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
//使用前序遍历
public void preorder(){
System.out.println(this.value);
//向左递归
if(this.left!=null){
this.left.preorder();//这个左节点的中序遍历
}
//向右递归
if(this.right!=null){
this.right.preorder();//这个右节点的中序遍历
}
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
@Override
public int compareTo(Node o) {
return this.value-o.value;//实现从小到大排列
}
}
(霍夫曼编码应用还没有学习:p115-p126,12集,再说吧)
二叉–排序–树BST(按中序遍历的结果是排好序的------得益于:比本节点小的放在左边,比本节点大的放在右边)
//创建和遍历顺序二叉树
//添加节点的方法,满足二叉排序树的要求:比本节点小的放在左边,比本节点大的放在右边(递归方式)
public void add(NodeB node){
if(node==null){
return;
}
if(node.value<this.value){//比本节点小的放在左边
if(this.left==null){
this.left=node;
}else{
this.left.add(node);
}
}else{//比本节点大的放在右边
if(this.right==null){
this.right=node;
}else{
this.right.add(node);
}
}
}
//二叉排序树的删除(分情况讨论)
这是力扣题的原题–删除二叉搜索树的节点
找到要删除的节点和待删除节点的父节点(否则无法删除和连接原先的节点)
平衡二叉树AVL(针对二叉排序树的弊端:左大右旋,右大左旋)
左旋转AVL:(当你节点的右子树的高度比左子树的超过1时,就进行对这个节点的左旋转)
//左旋转AVL(针对这个节点的右子树的高度比左子树的高度大的值超过1的节点)
public void LeftRotate(){
NodeA newnode=new NodeA(this.value);
newnode.left=this.left;
newnode.right=this.right.left;
this.value=this.right.value;
this.right=this.right.right;
this.left=newnode;
}
右旋转AVL:(当你节点的左子树的高度比右子树的超过1时,就进行对这个节点的右旋转)
//右旋转AVL(针对这个节点的左子树的高度比右子树的高度大的值超过1的节点)
public void RightRotate(){
NodeA newnode=new NodeA(this.value);
newnode.right=this.right;
newnode.left=this.left.right;
this.value=this.left.value;
this.left=this.left.left;
this.right=newnode;
}
双旋转AVL:(右大左旋,左大右旋)
(针对:(左子树高度-右子树高度)大于1&&当前节点的左子树的右子树高度都大于它的左子树的高度《=先对当前节点的左子节点进行左旋转,再对当前节点进行右旋转)
(针对:(右子树高度-左子树高度)大于1&&当前节点的右子树的左子树高度都大于它的右子树的高度《=先对当前节点的右子节点进行右旋转,再对当前节点进行左旋转)
针对每次添加一个节点在原来的AVL树中时,都进行判断是不是添加完后仍然是AVL树,然后进行左旋转与右旋转
//添加完结点后,判断当前添加的节点是不是满足AVL树
if(this.rightHeight()-this.leftHeight()>1){
if(right!=null&&right.leftHeight()>right.rightHeight()){
//双旋转
//首先对当前节点的右节点进行右旋转
this.right.RightRotate();
//在进行左旋转
this.LeftRotate();
}else {
this.LeftRotate();//左旋转
}
return;//平衡后直接返回,否则继续执行下面的代码
}
if(this.leftHeight()-this.rightHeight()>1){
if(left!=null&&left.rightHeight()>left.leftHeight()){
//双旋转
//首先对当前节点的左节点进行左旋转
this.left.LeftRotate();
//再对当前节点进行右旋转
this.RightRotate();
}else{
this.RightRotate();//右旋转
}
return;//平衡后直接返回
}
多叉树
多用于mysql中的查找
2-3树
仍然要满足排序树的特点(左小-中中-右大)
没懂怎么拆????(好难)
step1:首先来考虑插在同层,必须满足2,3节点的要求,即一个节点内必须不超过2个关键字,同时不能破坏同层的深度
step2:同层不满足,就需要先向上层拆,不行就拆本层,但是不能破坏同层的深度!!!
B树、B+树、B*树(B:balance)----了解原理(无代码要求)
B树的每个节点都存放关键字(要查找的数据)
B+树只有叶子节点才真正的存放关键字,而且叶子节点存放的关键字排好了序
非叶子节点是稀疏索引,比二分查找能够更快的找到索引!!!相当于是把一段数据进行无数的分割了分割,每次在非叶子结点定位数据在分割的哪一段,而不是一直二分查找。最后在叶子节点进行稠密索引查找,此时查找可以使用二分查找。
B*树(了解一下):分配新节点的概率比B+树要低,空间使用率更高