顺序存储二叉树
代码实现
package com.atguigu.tree;
import java.security.PublicKey;
/**
*顺序存储二叉树
* 从数据存储来看,数组的存储方式包括树存储和数组存储可以相互转换,
* 即数组可以换成树,树也可以换成数组
* 顺序存储二叉树的特点
* 1.顺序存储二叉树通常只考虑完全二叉树!!
* 2.第n个元素的左子节点为2*n+1;注意:这个n指的是原数组的下标,数组是从0开始的
* 所以,这个也是从0开始的,这个计算的左子节点也是对应元素在数组中的下标。特别注意
* 3.第n个元素的右子节点为2*n+2;
* 4.第n个元素的父节点为(n-1)/2
* 5.n:表示二叉树中的第几个元素(按0开始编号),具体可以看图
*
* 习题:给你一个数组{1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历,
* 前序遍历的结果应该是1,2,4,5,3,6,7
*
*
* @author WZ
* @create 2021-10-17 18:17
*/
public class ArrBinaryTreeDemo {
public static void main(String[] args) {
int [] arr=new int[]{1,2,3,4,5,6,7};
//创建一个ArrBinaryTree对象
ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
// arrBinaryTree.preOrder(0);//按照根节点,应该先传0进去即可,但是这样写比较麻烦,一般使用重载的方法
//前序操作
System.out.println("前序遍历的操作");
arrBinaryTree.preOrder();
//中序操作
System.out.println("中序遍历的操作");
arrBinaryTree.midOrder();
//后序操作
System.out.println("后序遍历的操作");
arrBinaryTree.postOrder();
}
}
//编写一个ArrayBinaryTree,实现顺序存储二叉树的遍历
class ArrBinaryTree{
private int[] arr;//存储数据节点的数组(就是存储二叉树节点的数组)
//需要一个构造器,待会把数组传给我即可
public ArrBinaryTree(int[] arr) {
this.arr = arr;
}
//重载前序遍历的方法
public void preOrder(){
this.preOrder(0);
}
//编写一个方法完成顺序存储二叉树前序遍历操作
/**
*
* @param index 这个表示数组的下标,类似刚才分析的n
*/
public void preOrder(int index){
//如果数组为空,或者arr.length=0
if (arr==null|| arr.length==0){//arr=null,表示arr不指向任何对象,length=0表示指针指向一个长度为0的对象,判断null需要在前防止空指针异常
System.out.println("数组为空,不能按照二叉树的前序进行遍历");
}
//进行前序遍历
//输出当前的这个元素
System.out.println(arr[index]);
//先向左递归遍历
if ((index*2+1)<arr.length){//加上这个判断是为了防止角标越界
preOrder(2*index+1);
}
//然后向右递归
if ((index*2+2)<arr.length){
preOrder(2*index+2);
}
}
//中序方法的重载操作
public void midOrder(){
midOrder(0);
}
//完成顺序存储二叉树的中序操作
public void midOrder(int index){
//先判断数组是否为空或者长度为0
if (arr==null || arr.length==0){
System.out.println("数组为空,不能进行二叉树中序遍历操作");
}
//先向左递归
if ((2*index+1)<arr.length){
midOrder(2*index+1);
}
//中间
System.out.println(arr[index]);
//向右递归
if ((2*index+2)<arr.length){
midOrder(2*index+2);
}
}
//后序方法的重载操作
public void postOrder(){
postOrder(0);
}
//完成顺序存储二叉树的后序操作
public void postOrder(int index){
//先判断数组是否为空或者长度为0
if (arr==null || arr.length==0){
System.out.println("数组为空,不能进行二叉树后序遍历操作");
}
//先向左递归
if ((2*index+1)<arr.length){
postOrder(2*index+1);
}
//向右递归
if ((2*index+2)<arr.length){
postOrder(2*index+2);
}
System.out.println(arr[index]);
}
}
线索化二叉树
提出问题
主要是在8,10,14,这三个有左右指针没有利用上,还有6的右指针没有利用,线索二叉树主要是用来解决这个问题.
对于1)的理解n个节点的二叉链表中含有n+1个空指针域,比如上面总共有,1,2,6,8,10,14这六个节点,对应的空指针域是n+1也就是7个.
按照某种遍历的方式,如上面这个中序,排序是8,2,10,1,6,14,其中2的前驱节点是8,后驱节点是10
这个和遍历线索化二叉树放在了一起,方便一起复习
package com.atguigu.tree.threadebinarytree;
import jdk.dynalink.NamedOperation;
/**线索二叉树
*
* 1
* 3 6
*8 10 14
* 中序遍历的结果是8,3,10,1,14,6
* @author WZ
* @create 2021-10-18 22:15
*/
public class ThreadeBinaryTreeDemo {
public static void main(String[] args) {
//测试一把中序线索化二叉树的功能
HeroNode root = new HeroNode(1, "Tom");
HeroNode node2 = new HeroNode(3, "jack");
HeroNode node3= new HeroNode(6, "smith");
HeroNode node4= new HeroNode(8, "marry");
HeroNode node5= new HeroNode(10, "king");
HeroNode node6= new HeroNode(14, "dim");
//二叉树,后面我们要递归的创建,仙子啊简单处理手动创建
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//测试中序线索化
ThreadBinaryTree threadBinaryTree = new ThreadBinaryTree();
threadBinaryTree.setRoot(root);
threadBinaryTree.threadNodes();
//测试,找10这个节点,看其前驱或者后驱节点是否变成了3和1
HeroNode leftNode = node5.getLeft();
System.out.println("十号节点的前驱节点是"+leftNode);
System.out.println("十号节点的后继节点是"+node5.getRight());
//当线索化二叉树后,不能在使用原来的遍历方法了
// threadBinaryTree.infixOrder();
System.out.println("使用线索化的方式遍历线索化二叉树");
threadBinaryTree.threadList();//8.3.10.1.14.6
}
}
//定义ThreadBinaryTree 实现了线索化功能的二叉树
class ThreadBinaryTree{
//只需要一个根节点即可
private HeroNode root;
//为了实现线索化,需要创建要给指向当前节点的前驱节点的指针
//在递归进行线索化时,pre总是保留前一个节点
private HeroNode pre=null;
/*这个主要是在进行线索化某个点的时候,比如这里的指向10节点,将要线索的节点
,此时,我们需要一个前驱节点,而10的前驱节点在中序遍历的结果是3,就是让一个
节点去线索化,得有一个指针(属性),它始终是和当前节点构成一个前驱的关系
也就是如果有一个节点node指向10,那么还得有一个节点pre指向其前驱节点3
因为只有这样才有可能让node指向前驱节点,从而实现线索化。
*/
public void setRoot(HeroNode root) {
this.root = root;
}
//重载threadNodes
public void threadNodes(){
this.threadeNodes(root);
}
/*
调用此方法,先进去,node不为空,直接进入下一个循环,然后判断左子节点的数值类型,只要为0就一直往下走,直到找到8这个节点,退出循环
然后打印出8这个节点,接着判断下一个while循环,此时8节点右边类型为1,那么先得到8右边指针指向的节点就是3,然后输出3,此时3的右边节点
类型是0,那么直接退出循环,替换node节点,也就是此时node节点为3右边的10,
然后继续从头开始,10左边节点不为0,所以第一个while循环进不去,直接输出10,节点类型,然后进入下一个循环,因为10右节点类型为1,然后得到
右节点,1,输出1.....依此类推
*/
//遍历线索化二叉树的方法
public void threadList(){
//定义一个变量,存储当前遍历的节点,root开始
HeroNode node =root;
while (node!=null){
//先循环的找到leftType==1的节点,第一个找到的应该是8这个节点
//后边随着遍历而变化,因为当left==1时,说明该节点是按照线索化
//处理后的有效节点
while (node.getLefType()==0){
node=node.getLeft();//就一直找,直到找到node.getLeftType==1,的时候停下
}
//找到之后,打印当前节点
System.out.println(node);
//如果当前节点的右指针指向的是后继节点,就一直输出
while (node.getRightType()==1){
//获取到当前节点的后继节点
node=node.getRight();
System.out.println(node);
}
//while循环结束,说明就找到不等于1的,替换遍历的节点
node=node.getRight();
}
}
//编写对二叉树进行中序线索化的方法
/**
*
* @param node 这个就是当前需要线索化的节点
*/
public void threadeNodes(HeroNode node){
//如果node==null,就不能进行线索化
if (node==null){
return;
}
//中序线索化的步骤
//一、先线索化左子树
threadeNodes(node.getLeft());
//二、线索化当前节点
//处理当前节点的前驱节点
//以8节点来理解,8节点.left=null,8节点的.leftType=1
if (node.getLeft()==null){
//让当前节点的左指针指向前驱节点
node.setLeft(pre);
//修改当前节点的左指针类型,指向前驱节点
node.setLefType(1);
}
//处理后继节点;后继节点处理需要返回递归上一层,8是上层的左节点。。
//此时node指向3,而pre指向8
if (pre!=null && pre.getRight()==null){
//让前驱节点的右指针指向当前节点
pre.setRight(node);
//修改前驱节点的右指针类型
pre.setRightType(1);
}
//!!这句话尤其重要,每处理一个节点后,让当前节点是下一个节点的前驱节点
pre=node;
//三hou索化右子树
threadeNodes(node.getRight());
}
//前序遍历;真正的调用是从根节点来调的,就想前面所讲的哈希表
public void preOrder(){
if (this.root!=null){
this.root.preOrder();//谁调用指向谁preOrder()指向root
}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("当前二叉树为空,无法遍历");
}
}
//前序查找遍历
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;
}
}
//删除节点的操作
public void delNode(int no){
//首先判断root是否为空
if (root!=null){
//如果只有一个root节点,这里需要立即判断root是不是要删除的节点
if (root.getNo()==no){
root=null;//恰好是根节点,直接置空即可
}else{
//如果root不是,则就递归删除
root.delNode(no);
}
}else {
System.out.println("空树,不能删除~~");
}
}
}
//先创建HeroNode 节点
class HeroNode{
private int no;//英雄的编号
private String name;//英雄的名字
private HeroNode left;//指向左边的索引,默认null
private HeroNode right;//指向右边的索引,默认null
//说明
//1.如果leftType==0,表示指向左子树,如果为1则表示指向前驱节点
//2.如果rightType==0,表示指向右子树,如果为1表示指向后继节点
private int lefType;
private int rightType;
public void setLefType(int lefType) {
this.lefType = lefType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
public int getLefType() {
return lefType;
}
public int getRightType() {
return rightType;
}
public HeroNode(int no, String name) {
this.no = no;
this.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;
}
public String getName() {
return name;
}
public HeroNode getLeft() {
return left;
}
public HeroNode getRight() {
return right;
}
//需要输出当前节点的内容,所以需要重写toString方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
//编写,前序遍历的方法
public void preOrder(){
//先输出当前节点,不用判断是否为空,因为都进来了肯定不为空
System.out.println(this);//先输出父节点,不能写root,后面无法递归,第一次this=root
//递归向左子树前序遍历
if (this.left!=null){
this.left.preOrder();
}
//向左递归结束后,返回到root,递归向右子树进行遍历
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);
}
//前序遍历查找的方法
/**
*
* @param no 英雄的编号(id)
* @return 找到返回该英雄node,没找到返回null
*/
public HeroNode preOrderSearch(int no){
System.out.println("进入前序遍历查找的方式");
//比较当前节点是不是
if (this.no==no){
return this;//
}
//如果不是,判断左子节点是否为空,不为空就左递归;
// 如果左递归找到,节点,则返回
HeroNode resNode=null;//假定我们找到的节点就是它,因为我们需要判断左递归找到要返回
if (this.left!=null){
resNode=this.left.preOrderSearch(no);
}
if (resNode!=null){//这个说明左子树我们已经找到了
return resNode;
}
//左递归没有找到的话,需要继续判断
//判断当前节点的右子节点是否为空,如果不为空,则继续向右递归查找,找到返回,没有返回null
if (this.right!=null){
resNode=this.right.preOrderSearch(no);
}
return resNode;//不管找没找到,就可以直接返回
}
//中序遍历查找
public HeroNode infixOrderSearch(int no){
//先判断当前节点的左子节点是否为空,如果不为空,则递归中序查找
HeroNode resNode=null;
if (this.left!=null){
resNode=this.left.infixOrderSearch(no);
}
if (resNode!=null){//这个表示左节点找到
return resNode;
}
System.out.println("进入zhong序遍历查找");
//如果没有找到,就和当前节点比较,如果是,则返回当前节点
if (this.no==no){
return this;//说明找到,并返回了当前节点
}
//如果没有找到,就向右继续递归中序查找
if (this.right!=null){
resNode=this.right.infixOrderSearch(no);
}
return resNode;//不管是否为空,直接返回即可
}
//后序遍历查找
public HeroNode postOrderSearch(int no){
//判断当前节点的左子节点是否为空,如果不为空,则递归后序查找
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("进入后序查找");//查看遍历的次数,得写在比较语句(this.no==no)的前面,不能写在前面
//如果左右子树都没有找到,就比较当前节点是不是
if (this.no==no){
return this;
}
return resNode;
}
//递归删除节点
//规定:1.如果是叶子节点则删除该节点,2如果删除的是非叶子节点,则删除该子树
public void delNode(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.delNode( no);
}
//左递归没有结束的话,就进行右递归进行删除
if (this.right!=null){
this.right.delNode(no);
}
}
}