java 数据结构---树---持续补充

网页右边,向下滑有目录索引,可以根据标题跳转到你想看的内容
如果右边没有就找找左边
主文章:https://blog.csdn.net/grd_java/article/details/120526149

一、二叉树

为什么需要树
  1. 数组的优点是,通过下标访问,速度快,有序数组,可以通过二分查找提高检索效率,但是如果要检索某个值,或者从中间插入一个值,需要移动整体,效率就很低了
  2. 链式存储优点是,一定程度上优化数组存储方式,插入和删除效率提高,不用整体移动了,但是检索时效率却异常的低,比如检索特定的一个值,需要遍历整个链表
  3. 树,就是提高数据存储和读取的效率,比如二叉排序树,既可以保证数据检索速度,又保证数据插入,删除,修改速度。
    在这里插入图片描述
树的概念
  1. 下图中有一个错误,H、E、F、G没有子节点,称为叶子节点,图中将H写成了A
    在这里插入图片描述
二叉树概念
  1. 二叉树就是每个节点最多有两个子节点,而形成的树形数据结构
  2. 如果二叉树的所有叶子节点(没有子节点的节点称为叶子节点)都在最后一层,并且节点总数=2^n-1(n为层数),则称为满二叉树。
  3. 如果二叉树的所有叶子节点都在倒数第一或倒数第二层,最后一层的叶子节点在左边连续,倒数第二层叶子节点在右边连续,称为完全二叉树
    在这里插入图片描述
    在这里插入图片描述
二叉树遍历:前序、中序、后序遍历

以这颗树为例
在这里插入图片描述

  1. 前序遍历:先输出父节点,再遍历左子树和右子树
  2. 中序遍历:先遍历左子树,再输出父节点,再遍历右子树
  3. 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
  4. 根据父节点的顺序即可判断是那种遍历,父节点最先遍历就是前序,父节点在左子树后面遍历就是中序,父节点最后遍历就是后序
  5. 通过遍历,我们就可以写出查找,删除的代码
  6. 运行效果
    在这里插入图片描述
  7. 代码
public class Test {

    public static void main(String[] args) {
        BinaryTreeNode[] binaryTreeNodes = new BinaryTreeNode[7];
        for(int i = 0 ;i<binaryTreeNodes.length;i++){
            binaryTreeNodes[i] = new BinaryTreeNode(i,i+1);
        }

        //人为构建二叉树
        binaryTreeNodes[0].setLeftNode(binaryTreeNodes[1]);
        binaryTreeNodes[0].setRightNode(binaryTreeNodes[2]);
        binaryTreeNodes[1].setLeftNode(binaryTreeNodes[3]);
        binaryTreeNodes[1].setRightNode(binaryTreeNodes[4]);
        binaryTreeNodes[2].setLeftNode(binaryTreeNodes[5]);
        binaryTreeNodes[2].setRightNode(binaryTreeNodes[6]);

        BinaryTree binaryTree = new BinaryTree(binaryTreeNodes[0]);
        System.out.println("前序遍历================");
        binaryTree.preOrder();
        System.out.println("中序遍历================");
        binaryTree.midOrder();
        System.out.println("后序遍历================");
        binaryTree.postOrder();
        System.out.println("前序查找id为5节点"+binaryTree.preFind(5));
        System.out.println("中序查找id为5节点"+binaryTree.midFind(5));
        System.out.println("后序查找id为5节点"+binaryTree.postFind(5));
        System.out.println("删除id为5节点");
        binaryTree.deleteTreeById(5);
        binaryTree.preOrder();
        System.out.println("删除id为2的节点,包括它的所有子节点");
        binaryTree.deleteTreeById(2);
        binaryTree.preOrder();
    }
}

/**
 * 二叉树节点
 */
class BinaryTreeNode{
    private int id;
    private Object data;
    private BinaryTreeNode leftNode;
    private BinaryTreeNode rightNode;

    public BinaryTreeNode(int id, Object data) {
        this.id = id;
        this.data = data;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public BinaryTreeNode getLeftNode() {
        return leftNode;
    }

    public void setLeftNode(BinaryTreeNode leftNode) {
        this.leftNode = leftNode;
    }

    public BinaryTreeNode getRightNode() {
        return rightNode;
    }

    public void setRightNode(BinaryTreeNode rightNode) {
        this.rightNode = rightNode;
    }

    @Override
    public String toString() {
        return "BinaryTreeNode{" +
                "id=" + id +
                ", data=" + data +
                '}';
    }

    /**
     * 前序遍历,先输出当前节点,然后遍历左子树,然后遍历右子树
     */
    public void preOrder(){
        //1. 先输出当前节点
        System.out.println(this+" ");
        //2. 如果左子树存在,遍历左子树
        if(this.leftNode!=null){
            this.leftNode.preOrder();
        }
        //3. 如果右子树存在,遍历右子树
        if(this.rightNode!=null){
            this.rightNode.preOrder();
        }
    }
    /**
     * 前序查找,先判断当前节点是否是查找的节点,不是遍历左子树,左子树没找到遍历右子树,都没找到返回null
     */
    public BinaryTreeNode preFind(Integer id){
        //1. 先判断当前节点
        if(id == this.id) return this;

        BinaryTreeNode node = null;//用来保存结果

        //2. 如果左子树存在,遍历左子树
        if(this.leftNode!=null){
            node =  this.leftNode.preFind(id);
        }
        if(node != null){//如果node不为null,那么表示在左子树找到了,直接返回,无需执行下面找右子树的代码
            return node;
        }
        //3. 如果右子树存在,遍历右子树
        if(this.rightNode!=null){
            node = this.rightNode.preFind(id);
        }
        return node;
    }
    /**
     * 中序遍历,先遍历左子树,然后输出当前节点,然后遍历右子树
     */
    public void midOrder(){
        //1. 如果左子树存在,遍历左子树
        if(this.leftNode!=null){
            this.leftNode.midOrder();
        }
        //2. 然后输出当前节点
        System.out.println(this+" ");

        //3. 如果右子树存在,遍历右子树
        if(this.rightNode!=null){
            this.rightNode.midOrder();
        }
    }
    /**
     * 中序查找
     */
    public BinaryTreeNode midFind(Integer id){

        BinaryTreeNode node = null;//用来保存结果

        //1. 如果左子树存在,遍历左子树
        if(this.leftNode!=null){
            node =  this.leftNode.preFind(id);
        }
        if(node != null){//如果node不为null,那么表示在左子树找到了,直接返回,无需执行下面找右子树的代码
            return node;
        }

        //2. 没有左子树了,判断是否和自己一致
        if(id == this.id) return this;

        //3. 如果右子树存在,遍历右子树
        if(this.rightNode!=null){
            node = this.rightNode.preFind(id);
        }
        return node;
    }
    /**
     * 后序遍历,先遍历左子树,然后遍历右子树,最后输出当前节点
     */
    public void postOrder(){
        //1. 如果左子树存在,遍历左子树
        if(this.leftNode!=null){
            this.leftNode.postOrder();
        }
        //2. 如果右子树存在,遍历右子树
        if(this.rightNode!=null){
            this.rightNode.postOrder();
        }
        //3. 然后输出当前节点
        System.out.println(this+" ");
    }
    /**
     * 后序查找
     */
    public BinaryTreeNode postFind(Integer id){

        BinaryTreeNode node = null;//用来保存结果

        //1. 如果左子树存在,遍历左子树
        if(this.leftNode!=null){
            node =  this.leftNode.preFind(id);
        }
        if(node != null){//如果node不为null,那么表示在左子树找到了,直接返回,无需执行下面找右子树的代码
            return node;
        }
        //2. 如果右子树存在,遍历右子树
        if(this.rightNode!=null){
            node = this.rightNode.preFind(id);
        }
        if(node != null){//如果node不为null,那么表示在右子树找到了,直接返回,无需执行下面找自己的代码
            return node;
        }
        //3. 判断是否和自己一致
        if(id == this.id) return this;
        return node;
    }

    /**
     * 根据id删除以它为根的子树,就是比如我们要删除5这个结点,那么我们删除它本身以及它所有子节点
     * 因为树是单向数据结构,我们如果找到这个结点本身,无法回到它的父节点断开引用链接,所以我们判断条件为子节点的id是否相匹配
     * 也就是说,这里无法判断root是否是要删除的节点,也没办法删除它
     */
    public void deleteTreeById(Integer id){
        //1、 判断左节点是否就是要删除的节点,如果是直接断开链接
        if(this.leftNode != null && this.leftNode.id == id) {
            this.leftNode = null;
            return;
        }
        //2、 判断右节点是否是要删除结点
        if(this.rightNode != null &&this.rightNode.id == id){
            this.rightNode = null;
            return;
        }
        //3、如果左右结点都不是要删除的节点,那么递归
        if(this.leftNode != null ) this.leftNode.deleteTreeById(id);
        if(this.rightNode != null ) this.rightNode.deleteTreeById(id);
    }

}

/**
 * 二叉树
 */
class BinaryTree{
    private BinaryTreeNode root;//根节点,只要有根节点,就可以找到整棵树

    public BinaryTree(BinaryTreeNode root) {
        this.root = root;
    }

    /**
     * 判断二叉树是否为空
     * @return true 二叉树为空,false 二叉树不为空
     */
    public boolean isNull(){
        if(root == null){
            System.out.println("二叉树为空!!!");
            return true;
        }
        return false;
    }

    //前序遍历
    public void preOrder(){
        if(isNull()) return;//如果二叉树为空,不执行遍历
        root.preOrder();
    }
    //中序遍历
    public void midOrder(){
        if(isNull()) return;
        root.midOrder();
    }
    //后序遍历
    public void postOrder(){
        if(isNull()) return;
        root.postOrder();
    }
    //前序查找
    public BinaryTreeNode preFind(Integer id){
        if(isNull()) return null;//如果二叉树为空,不执行遍历
        return root.preFind(id);
    }
    //中序查找
    public BinaryTreeNode midFind(Integer id){
        if(isNull()) return null;
        return root.midFind(id);
    }
    //后序查找
    public BinaryTreeNode postFind(Integer id){
        if(isNull()) return null;
        return root.postFind(id);
    }

    /**
     * 根据id删除子树
     */
    public void deleteTreeById(Integer id){
        if (isNull()) return;
        if(root.getId() == id){//如果根节点就是要删除的节点,那么直接置为空
            root = null;
        }else{//否则进行root的子树删除
            root.deleteTreeById(id);
        }
    }
}

1. 顺序存储二叉树

顺序存储二叉树
  1. 以数组存放树的节点,遍历数组时,依旧按照前序,中序和后序的顺序遍历
  1. 顺序二叉树通常只考虑完全二叉树
  2. 第n个元素的左子节点为2n+1,右子节点为2n+2,父节点为(n-1)/2. —n表示二叉树的第几个元素,从0开始
    在这里插入图片描述
  1. 有啥用?
  1. 八大排序算法的堆排序,就会用到,包括类似的场景都可以用,很好用,后面会讲到
  1. 代码
public class ArrBinaryTreeDemo {

	public static void main(String[] args) {
		int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
		//创建一个 ArrBinaryTree
		ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
		arrBinaryTree.preOrder(); // 1,2,4,5,3,6,7
	}

}

//编写一个ArrayBinaryTree, 实现顺序存储二叉树遍历

class ArrBinaryTree {
	private int[] arr;//存储数据结点的数组

	public ArrBinaryTree(int[] arr) {
		this.arr = arr;
	}
	
	//重载preOrder
	public void preOrder() {
		this.preOrder(0);
	}
	
	//编写一个方法,完成顺序存储二叉树的前序遍历
	/**
	 * 
	 * @param index 数组的下标 
	 */
	public void preOrder(int index) {
		//如果数组为空,或者 arr.length = 0
		if(arr == null || arr.length == 0) {
			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);
		}
	}
	
}

2. 线索化二叉树

线索化二叉树
  1. 解决了指针浪费问题
  2. 让空闲的指针指向自己的前后节点
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  3. 代码
  1. 首先我们再节点中添加两个属性,分别代表左、右指针的状态,看它是指向子树,还是前驱或后继
    在这里插入图片描述
  2. 然后,我们再二叉树中,创建一个属性,代表当前节点的上一个节点
    在这里插入图片描述
  3. 编写方法并测试(注意,线索化二叉树,需要使用新的遍历方式,线性遍历,效率更高,结果和非线索化二叉树遍历结果一样)
    在这里插入图片描述
    //为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
    //在递归进行线索化时,pre 总是保留前一个结点
    private BinaryTreeNode pre = null;

    

    //重载一把threadedNodes方法
    public void threadedNodes() {
        this.threadedNodes(root);
    }

    //编写对二叉树进行中序线索化的方法
    /**
     *
     * @param node 就是当前需要线索化的结点
     */
    public void threadedNodes(BinaryTreeNode node) {

        //如果node==null, 不能线索化
        if(node == null) {
            return;
        }

        //(一)先线索化左子树
        threadedNodes(node.getLeftNode());
        //(二)线索化当前结点[有难度]

        //处理当前结点的前驱结点
        //以8结点来理解
        //8结点的.left = null , 8结点的.leftType = 1
        if(node.getLeftNode() == null) {
            //让当前结点的左指针指向前驱结点
            node.setLeftNode(pre);
            //修改当前结点的左指针的类型,指向前驱结点
            node.setLeftType(1);
        }

        //处理后继结点
        if (pre != null && pre.getRightNode() == null) {
            //让前驱结点的右指针指向当前结点
            pre.setRightNode(node);
            //修改前驱结点的右指针类型
            pre.setRightType(1);
        }
        //!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
        pre = node;

        //(三)在线索化右子树
        threadedNodes(node.getRightNode());


    }

    //遍历线索化二叉树的方法
    public void threadedList() {
        //定义一个变量,存储当前遍历的结点,从root开始
        BinaryTreeNode node = root;
        while(node != null) {
            //循环的找到leftType == 1的结点,第一个找到就是8结点
            //后面随着遍历而变化,因为当leftType==1时,说明该结点是按照线索化
            //处理后的有效结点
            while(node.getLeftType() == 0) {
                node = node.getLeftNode();
            }

            //打印当前这个结点
            System.out.println(node+"left指向"+node.getLeftNode());
            //如果当前结点的右指针指向的是后继结点,就一直输出
            while(node.getRightType() == 1) {
                //获取到当前结点的后继结点
                node = node.getRightNode();
                System.out.println(node+"right指向"+node.getRightNode());
            }
            //替换这个遍历的结点
            node = node.getRightNode();

        }
    }

3. 二叉排序树(BST)

  1. BST(Binary Sort(Search) Tree),二叉排序树就是任何一个非叶子结点,要求左子结点的值比当前结点的值小,右子结点的值比当前结点的值大。如果有相同的值,可以将该结点放在左或右子结点
    在这里插入图片描述
二叉树的构建、添加和删除
  1. 构建
  1. 比如将{7,3,10,12,5,1,9}构建成排序二叉树,依次遍历数据,第一个直接就是根结点,剩下的按照规则,小的在左边,大的在右边,依次添加即可
  1. 添加
  1. 添加就是从根结点开始往下找,如果他比根结点大,就比较右边,如果右子结点没值,直接添加,如果有,就和右子结点比较,依次类推
  1. 删除
    在这里插入图片描述
  1. 删除叶子结点,比如2,5,9,12,直接删除即可
  2. 删除有一颗子树的节点,比如1,直接让它的子树来到它的位置,然后删掉自己
  3. 删除有两颗子树的节点,比如7,3,10,让它的右子树来到它的位置,然后删掉自己,然后让左子树按排序树规则添加到右子树,比如删除7,让10和它的子树到它的位置,然后3和它的子树添加到9的左子树
  4. 删除结点时,如果当前结点的子结点,就是要删除的结点,那么开始执行逻辑
  5. 首先判断要删除的结点时1,2,3种情况的那种,然后进行相应操作
代码
  1. 运行效果
    在这里插入图片描述
  2. 代码
import java.util.Arrays;

public class Test {

    public static void main(String[] args) {
        int[] arr = {7,3,10,12,5,1,9};
        System.out.println("将数组"+ Arrays.toString(arr)+"构建成二叉排序树=================");
        BinarySortTree binarySortTree = new BinarySortTree(arr);
        System.out.println("二叉排序树中序遍历结果为======================================");
        binarySortTree.midOrder();
        System.out.println("添加一个结点2");binarySortTree.add(2);binarySortTree.midOrder();
        System.out.println("删除结点2");binarySortTree.deleteNode(2);binarySortTree.midOrder();
        System.out.println("删除结点5");binarySortTree.deleteNode(5);binarySortTree.midOrder();
        System.out.println("删除结点9");binarySortTree.deleteNode(9);binarySortTree.midOrder();
        System.out.println("删除结点12");binarySortTree.deleteNode(12);binarySortTree.midOrder();
        System.out.println("删除结点7");binarySortTree.deleteNode(7);binarySortTree.midOrder();
        System.out.println("删除结点3");binarySortTree.deleteNode(3);binarySortTree.midOrder();
        System.out.println("删除结点10");binarySortTree.deleteNode(10);binarySortTree.midOrder();
        System.out.println("删除结点1");binarySortTree.deleteNode(1);binarySortTree.midOrder();
    }

}

/**
 * 二叉排序树结点
 */
class BinarySortTreeNode{
    private int data;
    private BinarySortTreeNode left;
    private BinarySortTreeNode right;

    public BinarySortTreeNode(int data) {
        this.data = data;
    }

    /**
     * 二叉排序树添加结点方法
     * @param node 一个结点
     */
    public void add(BinarySortTreeNode node){
        if(node == null) return;//如果结点为null,不进行添加
        //如果要添加结点比当前结点小,那么添加到左子结点
        if(node.getData()<this.data){
            if(this.left==null){//如果左子结点为空
                this.left = node;//添加到左子结点
            }else{//如果不为空
                this.left.add(node);//进行递归,让左子树添加
            }
        }else if(node.getData()>this.data){//如果要添加结点比当前结点大,那么添加到右子结点
            if(this.right==null){//如果右子结点为空,将结点添加
                this.right = node;
            }else {//不为空
                this.right.add(node);//递归到右子树添加
            }
        }else{//如果要添加结点等于当前结点,添加到左右都可以
            if(this.left==null){
                this.left = node;
            }else if(this.right == null){
                this.left = node;
            }
            //如果左右子节点都有了,那么放在右子树
            else{
                this.right.add(node);
            }
        }
    }

    /**
     * 中序遍历,先遍历左子树,然后输出当前节点,然后遍历右子树
     */
    public void midOrder(){
        //1. 如果左子树存在,遍历左子树
        if(this.left!=null){
            this.left.midOrder();
        }
        //2. 然后输出当前节点
        System.out.print(this+" ");

        //3. 如果右子树存在,遍历右子树
        if(this.right!=null){
            this.right.midOrder();
        }
    }

    /**
     * 中序查找指定数据的父节点
     */
    public BinarySortTreeNode midFindParent(Integer id){

        BinarySortTreeNode node = null;//用来保存结果

        //1. 如果左子树存在,判断左子结点是否是要找的值,不是继续遍历左子树
        if(this.left!=null&&this.left.getData()!=id){
            node =  this.left.midFindParent(id);
        }
        if(node != null){//如果node不为null,那么表示在左子树找到了,直接返回,无需执行下面找右子树的代码
            return node;
        }

        //2. 没有左子树了,判断是否和自己一致
        if(this.left!=null){
            if(id==this.left.data) return this;
        }
        if(this.right!=null){
            if(id==this.right.data) return this;
        }


        //3. 如果右子树存在,遍历右子树
        if(this.right!=null&&this.right.getData()!=id){
            node = this.right.midFindParent(id);
        }
        return node;
    }

    public int getData() { return data; }


    public void setData(int data) { this.data = data; }

    public BinarySortTreeNode getLeft() { return left; }

    public void setLeft(BinarySortTreeNode left) { this.left = left; }

    public BinarySortTreeNode getRight() { return right; }

    public void setRight(BinarySortTreeNode right) { this.right = right; }

    @Override
    public String toString() { return "Node{" + "data=" + data + '}'; }
}
/**
 * 二叉排序树
 */
class BinarySortTree{
    private BinarySortTreeNode root;

    public BinarySortTreeNode getRoot() { return root; }

    public BinarySortTree(){}

    //构造方法,通过数组直接构造排序二叉树
    public BinarySortTree(int[] arr){ this.addArrayToBinarySortTree(arr); }
    /**
     * 添加结点方法
     * @param node 一个结点
     */
    public void add(BinarySortTreeNode node){
        if(node == null) throw new RuntimeException("要添加结点为空!!!");
        if(root == null) root = node;
        else root.add(node);
    }

    /**
     * 重载直接将一个数据添加到排序二叉树
     * @param data
     */
    public void add(int data){
        add(new BinarySortTreeNode(data));
    }

    /**
     * 查找指定结点的父节点
     * @param data
     * @return
     */
    public BinarySortTreeNode findParentByData(int data){
        if(root.getData() == data) return null;
        return root.midFindParent(data);
    }

    /**
     * 删除根结点
     */
    private void deleteRoot(){
        if(root.getLeft()==null&&root.getRight()==null){
            root=null;
        }else if(root.getLeft()!=null&&root.getRight()!=null){
            BinarySortTreeNode left = root.getLeft();
            root = root.getRight();
            add(left);
        }else if(root.getLeft()==null&&root.getRight()!=null){
            root=root.getRight();
        }else if(root.getLeft()!=null&&root.getRight()==null){
            root=root.getLeft();
        }
    }
    /**
     * 删除非根结点
     * @param node 要删除结点
     * @param nodeParent 要删除结点的父结点
     * @param flag 要删除结点是父结点的左子结点还是右子结点,左为true
     */
    private void deleteNode(BinarySortTreeNode node,BinarySortTreeNode nodeParent,Boolean flag){
        if (node.getLeft()==null&&node.getRight()==null){//如果要删除结点是叶子结点,直接删除
            if(flag) nodeParent.setLeft(null);//如果它是父结点的左子结点,直接让父结点的left置为null
            else nodeParent.setRight(null);//如果是右子结点,让right置为null
        }else if(node.getLeft()!=null && node.getRight()==null){//如果要删除结点是只有右子树的结点
            //让右子树到它的位置
            if(flag) nodeParent.setLeft(node.getLeft());
            else nodeParent.setRight(node.getRight());
        }else if(node.getRight()!=null&&node.getLeft()==null){//如果要删除结点是只有左子树的结点
            //让左子树到它的位置
            if(flag)nodeParent.setLeft(node.getRight());
            else nodeParent.setRight(node.getRight());
        }else{//如果要删除结点拥有左右两个子树
            //让右子树到它的位置,然后左子树按规则添加到右子树中
            BinarySortTreeNode left = node.getLeft();//先记录左子树
            if(flag){
                nodeParent.setLeft(node.getRight());//然后让它的右子树到它原来的位置
                node.getRight().add(left);//然后将左子树按规则添加到右子树
            }else{
                nodeParent.setRight(node.getRight());
                node.getRight().add(left);
            }
        }
    }
    /**
     * 删除指定单个结点,如果有一样的只删除一个
     * @param data
     */
    public void deleteNode(int data){
        if(root == null) throw new RuntimeException("二叉排序树为空");

        BinarySortTreeNode nodeParent = null;//要删除节点的父节点
        BinarySortTreeNode node = null;//要删除的节点
        boolean flag = true;//要删除节点是父节点左子结点True,还是右子结点False

        if(root.getData()==data){//如果是根结点
            deleteRoot();//删除根结点
            return;
        }
        //获取要删除结点的父结点
        nodeParent = findParentByData(data);
        //获取要删除结点,并用flag记录结点时父结点的左子结点还是右子结点
        if(nodeParent.getLeft()!=null&&nodeParent.getLeft().getData()==data){
           node = nodeParent.getLeft();
           flag = true;
        }else{
            node = nodeParent.getRight();
            flag = false;
        }
        if(node==null){
            System.out.println("没有找到要删除的节点");
            return;
        }else{
            deleteNode(node,nodeParent,flag);
        }

    }

    /**
     * 中序遍历
     */
    public void midOrder(){
        if(root == null) System.out.println("二叉排序树为空!!!");
        else {
            root.midOrder();
            System.out.println();
        }
    }

    /**
     * 通过数组添加元素到二叉树
     * @param arr
     */
    public void addArrayToBinarySortTree(int[] arr){
        for(int i : arr){
            BinarySortTreeNode binarySortTreeNode = new BinarySortTreeNode(i);
            add(binarySortTreeNode);
        }
    }
}

4. 平衡二叉树(AVT)

平衡二叉树,就是更完善的二叉排序树,代码也是在二叉排序基础上添加了一些逻辑
  1. 平衡二叉树(AVL),又名平衡二叉搜索树,保证查询效率较高
  2. 它是一课空树或它的左右两个子树的高度差的绝对值小于等于1,并且左右两个子树都是一课平衡二叉树。满足这些特点就是平衡二叉树。(下图就是一课树,右子树高度比左子树高2,不满足,所以进行左旋转操作,后面会讲)
    在这里插入图片描述
  3. 常用实现算法有红黑树、AVL算法、替罪羊树、Treap、伸展树等
为什么要有平衡二叉树

在这里插入图片描述

  1. 当我们要将一个递增的序列转换成二叉排序树时,那么会变成一个效率很低的单链表,因为需要判断左右子树
  2. 而平衡二叉树可以解决此类问题
左旋转(以右子树比左子树高度,高1以上,比如2,就需要左旋转,反之右旋转)

在这里插入图片描述

右旋转

在这里插入图片描述

双旋转
  1. 某些序列,无法通过左旋转,或右旋转,转变为平衡二叉树,此时需要双旋转
    在这里插入图片描述
代码,只贴出和二叉排序树不同的代码块
  1. 运行效果
    在这里插入图片描述
  2. 代码(以下指出添加的代码,和修改的代码)
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    /**
     * 返回左子树高度
     */
    public int leftHeight(){
        if(left == null){
            return 0;
        }
        return left.height();
    }
    /**
     * 返回右子树高度
     */
    public int rightHeight(){
        if(right == null){
            return 0;
        }
        return right.height();
    }
    /**
     * 返回当前结点为根结点的树的高度
     */
    public int height(){
        return Math.max(left == null?0:left.height(),right == null?0:right.height())+1;
    }

    /**
     * 以当前结点为根结点进行左旋转
     */
    public void leftRotate(){
        //1.创建新结点,保存当前根结点
        BinarySortTreeNode binarySortTreeNode = new BinarySortTreeNode(data);
        //2.左子树变为新结点左子树
        binarySortTreeNode.setLeft(left);
        //3.右子树的左子树变为新结点的右子树
        binarySortTreeNode.setRight(right.getLeft());
        //4.让右子树上移成为根结点
        data = right.getData();
        right = right.getRight();
        //5.新结点作为左子树
        left = binarySortTreeNode;
    }

    /**
     * 以当前结点为根结点进行右旋转
     */
    public void rightRotate(){
        //1.创建新结点,保存当前根结点
        BinarySortTreeNode binarySortTreeNode = new BinarySortTreeNode(data);
        //2.右子树变为新结点右子树
        binarySortTreeNode.setRight(right);
        //3.左子树的右子树变为新结点的左子树
        binarySortTreeNode.setLeft(left.getRight());
        //4.让左子树上移成为根结点
        data = left.getData();
        left = left.getLeft();
        //5.新结点作为右子树
        right = binarySortTreeNode;
    }

    /**
     * 二叉排序树添加结点方法
     * @param node 一个结点
     */
    public void add(BinarySortTreeNode node){
        if(node == null) return;//如果结点为null,不进行添加
        //如果要添加结点比当前结点小,那么添加到左子结点
        if(node.getData()<this.data){
            if(this.left==null){//如果左子结点为空
                this.left = node;//添加到左子结点
            }else{//如果不为空
                this.left.add(node);//进行递归,让左子树添加
            }
        }else if(node.getData()>this.data){//如果要添加结点比当前结点大,那么添加到右子结点
            if(this.right==null){//如果右子结点为空,将结点添加
                this.right = node;
            }else {//不为空
                this.right.add(node);//递归到右子树添加
            }
        }else{//如果要添加结点等于当前结点,添加到左右都可以
            if(this.left==null){
                this.left = node;
            }else if(this.right == null){
                this.left = node;
            }
            //如果左右子节点都有了,那么放在右子树
            else{
                this.right.add(node);
            }
        }

        /**
         * 转变为平衡二叉树,双旋转
         */
        //如果右子树高度比左子树高度大,并且比1大,那么需要左旋转
        if(rightHeight() - leftHeight()>1){
            //如果右子树的左子树高度,大于右子树的右子树的高度,那么进行双旋转(先对右子树,右旋转)
            if(right!=null && right.leftHeight() > right.rightHeight()){
                right.rightRotate();
                //然后再左旋转
                leftRotate();
            }else{
                //然后再左旋转
                leftRotate();
            }
            return;//旋转完成后,不要继续下面的判断了
        }
        //如果左子树高度比右子树高度大,并且比1大,那么需要右旋转
        if(leftHeight() - rightHeight()>1){
            //如果左子树的右子树高度,大于左子树的左子树的高度,那么进行双旋转(先对左子树,左旋转)
            if(left!=null && left.rightHeight() > left.leftHeight()){
                left.leftRotate();
                //然后再右旋转
                rightRotate();
            }else{
                //然后再右旋转
                rightRotate();
            }
            return;
        }
    }

二、赫夫曼树

  1. 给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称为最优二叉树,又称哈夫曼树(Huffman Tree),或霍夫曼树或赫夫曼树
  2. 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
    在这里插入图片描述
  1. 13这个结点,权值是13,根结点层数为1的情况下,此结点的路径长度是2
  2. 这并不是一课赫夫曼树,请继续往下看
路径和路径长度
  1. 一棵树中,一个结点往下可以到达的孩子或孙子结点之间的通路,称为路径。
  2. 通路中分支的数目称为路径长度,若根节点层数为1,则根节点到第L层结点的路径长度为L-1
    在这里插入图片描述
  1. 根结点层数为1,13这个结点层数为3,3-1=2,那么13这个结点路径长度为2
结点的权和带权路径长度
  1. 权:树中结点赋予一个具有某种含义的数值,称为该结点的权
  2. 结点带权路径长度:根节点到该结点之间的路径长度与该结点的权的乘积
  1. 依然以上面的图片为例,13这个结点带权路径长度就是路径长度 *=2 *13=26
树的带权路径长度
  1. 所有叶子结点的带权路径长度之和,即为WPL(weighted path length),权值越大的结点离根结点越近的二叉树才是最优二叉树。
  2. WPL最小的就是赫夫曼树
    在这里插入图片描述
  1. 树的带权路径长度wpl是评价一课二叉树是否为赫夫曼树的指标,wpl最小的就是最赫夫曼树
  2. 树的wpl就是所有叶子节点的带权路径长度相加后的结果
  3. 上图可以看出,中间的树,满足权值越大的叶子结点离根节点越近(层数越靠近根结点层数)的条件,那么它就是最优二叉树

1. 创建赫夫曼树

如何创建赫夫曼树
  1. 假定数列{13,7,8,3,29,6,1}要转换成一棵赫夫曼树
  2. 先从小到大排序,将每一个数据(就是一个结点),看成是一颗最简单的二叉树
  3. 取出根结点权值最小的两颗二叉树(第一次取,就是1和3这两颗)
  4. 组成一棵新的二叉树,此时新二叉树的根结点权值是前面两颗二叉树根结点的权值之和(第一次就是1和3合并,根结点权值为1+3=4,那么此时就有了一颗{1,4,3}的二叉树)
  5. 最后再将新二叉树,以根结点的权值大小再次排序(第一次就是{1,4,3}这颗和{6}这颗),然后不断重复3-4-5的步骤,直到数列中,所有数据都被处理,就得到了赫夫曼树
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
代码
  1. 运行效果
    在这里插入图片描述
  2. 代码
import java.util.ArrayList;
import java.util.Collections;

public class Test {

    public static void main(String[] args) {
        int arr[] = {13,7,8,3,29,6,1};
        HuffmanTreeNode huffmanTree = createHuffmanTree(arr);
        System.out.println("====前序遍历结果====");
        preOrder(huffmanTree);
    }

    /**
     *  创建赫夫曼树
     * @param arr 序列
     */
    public static HuffmanTreeNode createHuffmanTree(int[] arr){
        //1.将arr中每个元素构建成结点,放入一个集合中
        ArrayList<HuffmanTreeNode> huffmanTreeNodes = new ArrayList<>();
        for(int value:arr){
            huffmanTreeNodes.add(new HuffmanTreeNode(value));
        }
        //不断重复 取结点---构成新二叉树----重新排序,直到集合中只剩下一个结点
        while(huffmanTreeNodes.size()>1){
            //2.排序,使用Collections接口的sort方法,对集合排序,要求集合中元素,必须实现Comparable接口,并从写compareTo方法,至于从小到大排,还是从大到小排,取决于compareTo方法的实现
            Collections.sort(huffmanTreeNodes);
            //3. 取出根结点最小的两颗二叉树,构建成新二叉树,然后删除最小两颗二叉树,将新二叉树放集合中,然后排序
            HuffmanTreeNode leftNode = huffmanTreeNodes.get(0);//取出集合第一个,也就是最小的
            HuffmanTreeNode rightNode = huffmanTreeNodes.get(1);//取出第二个,也就是第二小的
            HuffmanTreeNode parent = new HuffmanTreeNode(leftNode, rightNode);//然后构建一个新二叉树
            huffmanTreeNodes.remove(leftNode);//删除
            huffmanTreeNodes.remove(rightNode);//删除
            huffmanTreeNodes.add(parent);//将新二叉树放进集合
            Collections.sort(huffmanTreeNodes);//排序
        }

        return huffmanTreeNodes.get(0);
    }
    /**
     * 前序遍历赫夫曼树
     */
    public static void preOrder(HuffmanTreeNode node){
        if(node == null){
            System.out.println("赫夫曼树为空树");
            return;
        }
        node.preOrder();
    }
}

/**
 * 赫夫曼树结点类
 * 继承Comparable接口,用来比较结点之间的权值,需要实现compareTo方法
 */
class HuffmanTreeNode implements Comparable<HuffmanTreeNode> {
    private int value;//结点权值
    private HuffmanTreeNode left;//左子结点
    private HuffmanTreeNode right;//右子结点

    /**
     * 根据权值直接构建结点
     * @param value
     */
    public HuffmanTreeNode(int value) {
        this.value = value;
    }

    /**
     * 根据左右结点,构建新的二叉树,权值为左右结点的权值之和
     * @param left
     * @param right
     */
    public HuffmanTreeNode(HuffmanTreeNode left,HuffmanTreeNode right){
        this.left = left;
        this.right = right;
        this.value = left.getValue() + right.getValue();
    }

    @Override
    public int compareTo(HuffmanTreeNode o) {
        //从小到大排序,至于为什么,百度一下Comparable接口
        return this.value - o.value;
        //从大到小排
        //return -(this.value - o.value);
    }

    /**
     * 前序遍历,先输出当前节点,然后遍历左子树,然后遍历右子树
     */
    public void preOrder(){
        //1. 先输出当前节点
        System.out.println(this+" ");
        //2. 如果左子树存在,遍历左子树
        if(this.left!=null){
            this.left.preOrder();
        }
        //3. 如果右子树存在,遍历右子树
        if(this.right!=null){
            this.right.preOrder();
        }
    }

    @Override
    public String toString() {
        return "HuffmanTreeNode{" +
                "value=" + value +
                '}';
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public HuffmanTreeNode getLeft() {
        return left;
    }

    public void setLeft(HuffmanTreeNode left) {
        this.left = left;
    }

    public HuffmanTreeNode getRight() {
        return right;
    }

    public void setRight(HuffmanTreeNode right) {
        this.right = right;
    }


}

2. 赫夫曼编码

  1. 又称哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,一种程序算法
  2. 是赫夫曼树在电讯通讯中的经典应用之一
  3. 广泛应用于数据文件压缩,压缩率在20%~90%之间
  4. 是可变字长编码(VLC的一种),Huffman于1952年提出一种编码方法,称为最佳编码
首先看一下定长编码方式,就是将字符转换成Ascii码,然后取到对应2进制编码,传输

在这里插入图片描述

变长编码,就是统计每个字符出现的次数,次数越多,编码越小,然后按照编码,将其组成一串2进制编码,但是因为编码的前缀重复性,比如编码1和编码10,计算机不知道什么时候按1解析,什么时候按10解析,也就是说,不能匹配到重复编码

在这里插入图片描述

赫夫曼编码,将字符出现次数,作为权值,出现的越多,权值越大,根据权值构建赫夫曼树

在这里插入图片描述

  1. 根据权值创建赫夫曼树,向左的路径为0,向右的路径为1
    在这里插入图片描述
  2. 然后根据创建好的赫夫曼树,给各字符规定编码,可以发现,每个字符的编码,都不是其它字符编码的前缀,比如o的编码是1000,u的编码10010,如果o的编码是100,那么确实是10010的前缀,但是此编码,不会出现这种情况,那么赫夫曼编码,满足前缀编码的特性,不会造成匹配多义性
    在这里插入图片描述
  3. 额外的,如果出现权值相同的情况,比如有4个权值为4的二叉树,那么它们的位置并不固定,这样生成的赫夫曼树不一样,编码也完全不一样,但是wpl是一样的,所以如果出现这种情况也无伤大雅,压缩最后的结果长度,还是一样的
实现思路和代码
  1. 实现思路
  1. 赫夫曼树的结点,除了存放权值、左右子树,还要存放数据
  2. 得到"i like like like java do you like a java"对应的byte数组
  3. 生成结点,将结点放到List
  4. 创建赫夫曼树
  5. 获取赫夫曼编码,
  6. 获取字符串对应二进制编码,也就是压缩操作(压缩对应的byte数组,返回的也是byte数组)
  7. 解码,就是解压操作
  1. 运行效果
    在这里插入图片描述
  1. 代码
import java.util.*;

public class Test {

    public static void main(String[] args) {
        String str = "i like like like java do you like a java";
        //> > 2. 得到"i like like like java do you like a java"对应的byte数组
        byte[] strBytes = str.getBytes();

        System.out.println("源字符串和源字节数组==========================");
        System.out.println(str);
        System.out.println("压缩前字节数组"+Arrays.toString(strBytes));

        //获取压缩后字节数组和赫夫曼树编码表
        List<Object> list = HuffmanZip(strBytes);
        byte[] zip = (byte[]) list.get(0);//获取压缩后的字节数组
        HashMap<Byte, String> byteStringHashMap =(HashMap<Byte, String>) list.get(1);//获取赫夫曼编码表
       
        //> > 7. 解码,就是解压操作
        byte[] bytes = huffmanUnZip(zip, byteStringHashMap);//获取解压后字节数组

        System.out.println("最终解压效果"+new String(bytes));
    }

    /**
     * 将字节数组压缩,返回压缩编码后的字节数组
     * @param strBytes 初始的字节数组
     * @return 压缩后的字节数组
     */
    public static List<Object> HuffmanZip(byte[] strBytes){
        //> > 3. 生成结点,将结点放到List
        //> > 4. 创建赫夫曼树
        HuffmanTreeNode huffmanTree = createHuffmanTree(strBytes);

        //> > 5. 获取赫夫曼编码
        HashMap<Byte, String> byteStringHashMap = HuffmanCode(huffmanTree);

        System.out.println("每个字符对应的编码为==============================");
        for(Map.Entry<Byte,String> entry:byteStringHashMap.entrySet()){
            System.out.println(entry.getKey()+"    "+entry.getValue());
        }

        //> > 6. 获取字符串对应二进制编码,也就是压缩操作(压缩对应的byte数组,返回的也是byte数组)
        byte[] zip = zip(strBytes, byteStringHashMap);

        System.out.println("编码后的字符数组======================================");
        System.out.println(Arrays.toString(zip));

        //封装结果集,将压缩后字节数组,和赫夫曼编码表封装返回
        ArrayList<Object> objects = new ArrayList<>();
        objects.add(zip);
        objects.add(byteStringHashMap);
        return objects;
    }

    /**
     * 将压缩后的字节数组,解压
     * @param zip 压缩后字节数组
     * @param byteStringHashMap 赫夫曼编码表
     * @return 解压后的字节数组
     */
    public static byte[] huffmanUnZip(byte[] zip,HashMap<Byte, String> byteStringHashMap){
        //字节数组解压成二进制编码
        String s = byteToString(zip);
        System.out.println("解压后获取的二进制编码"+s);

        //将字符串按照赫夫曼编码,进行解码
        HashMap<String, Byte> stringByteHashMap = new HashMap<>();//先将赫夫曼编码表进行反转,就是反向查询,比如32->10  转换成 10->32
        for(Map.Entry<Byte,String> entry:byteStringHashMap.entrySet()){
            stringByteHashMap.put(entry.getValue(), entry.getKey());
        }

        System.out.println("将赫夫曼编码表进行反转==============================");
        for(Map.Entry<String,Byte> entry:stringByteHashMap.entrySet()){
            System.out.println(entry.getKey()+"    "+entry.getValue());
        }

        //通过反转后的赫夫曼编码表,将二进制串解析为正常字符,保存到list中
        ArrayList<Byte> list = new ArrayList<>();
        for(int i = 0;i<s.length();){//依次遍历二进制码,注意这里没有i++,因为i的增长在代码中控制
            int count = 1;//用来记录当前判断了多少个二进制码,因为赫夫曼编码的长度都不一样
            boolean flag = true;//while循环的条件
            Byte b = null;//用来保存通过二进制码找到的对应值
            while(flag){
                String key = s.substring(i,i+count);//每次都从当前索引位置,截取count个二进制码
                b = stringByteHashMap.get(key);//匹配map表中的值
                if(b==null){//如果没有匹配到,count++
                    count++;
                }else{//如果匹配到了,结束while循环,进行下一个字符匹配
                    flag = false;
                }
            }
            list.add(b);//将匹配到的值加入list
            i+=count;//让i从匹配完成后的位置开始
        }
        //将list中数据放在byte数组中返回
        byte b[] = new byte[list.size()];
        for(int i =0 ;i< b.length;i++){
            b[i] = list.get(i);
        }

        return b;
    }


    /**
     * > > 4. 创建赫夫曼树
     * @param bytes 字节数组,存储了字符串的对应字节,我们需要统计每个byte出现次数,当做权值,构建赫夫曼树
     */
    public static HuffmanTreeNode createHuffmanTree(byte[] bytes){
        //1.将arr中每个元素构建成结点,放入一个集合中
        ArrayList<HuffmanTreeNode> huffmanTreeNodes = new ArrayList<>();
        //存储每一个byte出现的次数,获取权值
        HashMap<Byte, Integer> counts = new HashMap<>();
        for(byte data:bytes){
            Integer count = counts.get(data);//从map中获取byte对应的值
            if(count == null){//如果当前map还没有统计过当前byte的出现次数,那么进行第一次存储
                counts.put(data,1);
            }else{
                counts.put(data,count+1);//如果当前map已经统计过,那么次数加1
            }
        }//此时每个字符的权值已经获取完毕
        //把每一个key-value队,转成一个结点对象加入到集合中
        for(Map.Entry<Byte,Integer> entry:counts.entrySet()){
            huffmanTreeNodes.add(new HuffmanTreeNode(entry.getKey(),entry.getValue()));
        }

        //不断重复 取结点---构成新二叉树----重新排序,直到集合中只剩下一个结点
        while(huffmanTreeNodes.size()>1){
            //2.排序,使用Collections接口的sort方法,对集合排序,要求集合中元素,必须实现Comparable接口,并从写compareTo方法,至于从小到大排,还是从大到小排,取决于compareTo方法的实现
            Collections.sort(huffmanTreeNodes);
            //3. 取出根结点最小的两颗二叉树,构建成新二叉树,然后删除最小两颗二叉树,将新二叉树放集合中,然后排序
            HuffmanTreeNode leftNode = huffmanTreeNodes.get(0);//取出集合第一个,也就是最小的
            HuffmanTreeNode rightNode = huffmanTreeNodes.get(1);//取出第二个,也就是第二小的
            HuffmanTreeNode parent = new HuffmanTreeNode(leftNode, rightNode);//然后构建一个新二叉树
            huffmanTreeNodes.remove(leftNode);//删除
            huffmanTreeNodes.remove(rightNode);//删除
            huffmanTreeNodes.add(parent);//将新二叉树放进集合
            Collections.sort(huffmanTreeNodes);//排序
        }

        return huffmanTreeNodes.get(0);
    }

    /**
     * > > 5. 获取赫夫曼编码
     * 生成赫夫曼树对应编码
     * @param node
     */
    public static HashMap<Byte,String> HuffmanCode(HuffmanTreeNode node){

        if(node == null) throw new RuntimeException("赫夫曼树为空!!!");

        //根据路径拼接每个叶子节点的赫夫曼编码
        StringBuilder stringBuilder = new StringBuilder();

        //将赫夫曼编码表放在Map中,比如空格,32 对应编码为01
        HashMap<Byte, String> huffmanCodes = new HashMap<>();

        getCodes(node,"",stringBuilder,huffmanCodes);

        return huffmanCodes;
    }

    /**
     * > > 5. 获取赫夫曼编码
     * 将结点的所有叶子节点的赫夫曼编码得到,并放入map中
     * @param node 赫夫曼树的结点
     * @param code 路径编码,左路径是0,右路径是1
     * @param stringBuilder 拼接路径
     * @param huffmanCodes 最终结果存放到这个map
     */
    private static void getCodes(HuffmanTreeNode node,String code,StringBuilder stringBuilder,HashMap<Byte,String> huffmanCodes){

        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);

        stringBuilder2.append(code);//将code路径拼接

        if(node != null){//如果node!=null,说明还可以遍历,这个结点不是一个无效结点
            //判断当前结点是否是叶子结点,如果不是叶子结点,它的data是null
            if(node.getData() == null){//如果是非叶子结点
                //向左递归
                getCodes(node.getLeft(),"0",stringBuilder2,huffmanCodes);
                //向右递归
                getCodes(node.getRight(),"1",stringBuilder2,huffmanCodes);
            }else{//如果是叶子结点
                //将其添加到hashMap中
                huffmanCodes.put(node.getData(),stringBuilder2.toString());
            }
        }
    }

    /**
     * > > 6. 获取字符串对应二进制编码,也就是压缩操作
     * @param bytes 压缩前字节数组
     * @param huffmanCodes 赫夫曼树编码表
     * @return 压缩后的字节数组
     */
    public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
        //用于拼接压缩后的二进制
        StringBuilder stringBuilder = new StringBuilder();
        for(byte b : bytes){//遍历byte数组
            //为每个byte查询相应编码,然后拼接到stringBuilder中
            stringBuilder.append(huffmanCodes.get(b));
        }
        System.out.println("压缩后的二进制编码"+stringBuilder.toString());

        //将二进制码,转换成byte[],如果不转,反而变大了,起不到压缩效果
        //获取byte[]长度,因为每个byte是8位,所以我们将stringBuilder/8就可以了
        int len;
//        if(stringBuilder.length()%8 == 0){//如果取余8==0,说明通过length/8获取的长度,刚好存放
//            len = stringBuilder.length()/8;
//        }else{//如果不等于0,说明/8后,还有余下的没地方存放,那么我们多加一个长度,用来存放这些多出来的
//            len = stringBuilder.length()/8+1;
//        }
        //下面这句的效果,等同于上面注释的代码效果
        len = (stringBuilder.length()+7)/8;


        //创建,存储压缩后的byte数组
        byte[] by = new byte[len];
        int index = 0;//by数组下标
        for(int i = 0;i<stringBuilder.length();i+=8){//字节数组,一个元素可以存储8为,所以步长为8
            String strByte;
            if(i+8>stringBuilder.length())//如果不够8为了,直接将当前下标到末尾的二进制代码取出来
                strByte = stringBuilder.substring(i);
            else//如果还够8位,直接从当前下标取8位
                strByte = stringBuilder.substring(i,i+8);
            //将取出的字串,放在字节数组中
            by[index] = (byte)Integer.parseInt(strByte,2);
            index++;//下标自增
        }

        return by;
    }


    /**
     * > > 7. 解码,就是解压操作
     * 将字符数组,转换成对应二进制字符串
     * @return 按补码形式返回
     */
    public static String byteToString(byte[] zip){
        StringBuilder stringBuilder = new StringBuilder();//保存最终结果
        boolean flag = true;//用于标记一个字节是否需要补高位
        for(int i = 0;i<zip.length;i++){
            int intNum = zip[i];//将byte转成一个int值

            flag = (i != zip.length-1);//如果是最后一个字节,无需补高位

            if(flag){//如果是true,我们需要补高位
                intNum |= 256;//按位与,举个例子:256的二进制是 1 0000 0000 ,1的二进制是0000 0001 | 1 0000 0000 = 1 0000 0001
            }

            String s = Integer.toBinaryString(intNum);//返回i对应的二进制补码
            //如果是true,我们取字符串后8位二进制
            if(flag){
                stringBuilder.append(s.substring(s.length()-8));
            }else{//如果不是,无需补高位,那么直接连接即可
                stringBuilder.append(s);
            }
        }


        return stringBuilder.toString();
    }

    /**
     * 前序遍历赫夫曼树
     */
    public static void preOrder(HuffmanTreeNode node){
        if(node == null){
            System.out.println("赫夫曼树为空树");
            return;
        }
        node.preOrder();
    }
}

/**
 * 赫夫曼树结点类
 * 继承Comparable接口,用来比较结点之间的权值,需要实现compareTo方法
 */
class HuffmanTreeNode implements Comparable<HuffmanTreeNode> {
    private Byte data;//存放数据本身,比如'a' = 97
    private int value;//结点权值
    private HuffmanTreeNode left;//左子结点
    private HuffmanTreeNode right;//右子结点

    /**
     * 根据权值和数据直接构建结点
     * @param value
     */
    public HuffmanTreeNode(Byte data, int value) {
        this.data = data;
        this.value = value;
    }

    /**
     * 根据左右结点,构建新的二叉树,权值为左右结点的权值之和
     * @param left
     * @param right
     */
    public HuffmanTreeNode(HuffmanTreeNode left,HuffmanTreeNode right){
        this.left = left;
        this.right = right;
        this.value = left.getValue() + right.getValue();
    }

    @Override
    public int compareTo(HuffmanTreeNode o) {
        //从小到大排序,至于为什么,百度一下Comparable接口
        return this.value - o.value;
        //从大到小排
        //return -(this.value - o.value);
    }

    /**
     * 前序遍历,先输出当前节点,然后遍历左子树,然后遍历右子树
     */
    public void preOrder(){
        //1. 先输出当前节点
        System.out.println(this+" ");
        //2. 如果左子树存在,遍历左子树
        if(this.left!=null){
            this.left.preOrder();
        }
        //3. 如果右子树存在,遍历右子树
        if(this.right!=null){
            this.right.preOrder();
        }
    }

    @Override
    public String toString() {
        return "HuffmanTreeNode{" +
                "data=" + data +
                ", value=" + value +
                '}';
    }

    public Byte getData() {return data;}

    public void setData(Byte data) {this.data = data;}

    public int getValue() {return value;}

    public void setValue(int value) {this.value = value;}

    public HuffmanTreeNode getLeft() {return left; }

    public void setLeft(HuffmanTreeNode left) {this.left = left;}

    public HuffmanTreeNode getRight() {return right; }

    public void setRight(HuffmanTreeNode right) {this.right = right;}


}

3. 使用赫夫曼编码压缩解压文件

这里只是介绍如何压缩文件与解压,解压文件时,需要根据不同文件类型来响应式解码,比如高清图片,中文字符,都需要特殊处理,与赫夫曼树编码无关,这里不做介绍

在这里插入图片描述

  1. 运行效果
    在这里插入图片描述
  2. 代码(只贴出了修改过和新增的代码)
    在这里插入图片描述
    public static void main(String[] args) {
        String srcFile = "E://a.txt";
        String dstFile = "E://a.zip";
        zipFile(srcFile,dstFile);
        System.out.println("压缩文件完成");

        String zipFile = "E://a.zip";
        String dstFile2 = "E://unZip_a.txt";
        unZipFile(zipFile,dstFile2);

    }

    /**
     * 压缩文件
     * @param srcFile 要压缩的文件路径
     * @param dstFile 压缩后保存路径
     */
    public static void zipFile(String srcFile,String dstFile){
        //创建文件输入流
        FileInputStream is = null;
        //创建文件输出流
        OutputStream os = null;
        //创建一个和文件输出流关联的对象输出流,这样我们可以利用对象流将一个对象写入压缩文件(一起输出),比如我们将赫夫曼编码表输出出去,那么日后想要解压,就可以直接拿到编码表解压
        ObjectOutputStream objectOutputStream=null;
        try {
            is = new FileInputStream(srcFile);//获取指定文件输入流
            byte[] b = new byte[is.available()];//创建对应字节数组
            is.read(b);//将文件内容读取到字节数组中

            //压缩,获取赫夫曼编码表和压缩后字节数组
            List<Object> list = HuffmanZip(b);
            byte[] zip = (byte[]) list.get(0);//获取压缩后的字节数组
            HashMap<Byte, String> byteStringHashMap =(HashMap<Byte, String>) list.get(1);//获取赫夫曼编码表

            //创建文件输出流,存放压缩后文件
            os = new FileOutputStream(dstFile);
            //创建一个和文件输出流关联的ObjectOutputStream
            objectOutputStream = new ObjectOutputStream(os);
            //首先将压缩后字节数组写入输出流,这样就完成了压缩文件的创建
            objectOutputStream.writeObject(zip);
            //以对象流的方式,将赫夫曼编码写入压缩文件,恢复源文件时使用
            objectOutputStream.writeObject(byteStringHashMap);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                objectOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();//关闭流
            } catch (IOException e) {
                e.printStackTrace();
            }


        }
    }

    public static void unZipFile(String zipFile,String dstFile){
        //创建文件输入流
        InputStream is = null;
        //定义和文件输入流关联的对象输入流
        ObjectInputStream ois = null;
        //文件输出流
        OutputStream os = null;
        try {
            is = new FileInputStream(zipFile);
            ois = new ObjectInputStream(is);
            //获取压缩后文件
            byte[] zip = (byte[])ois.readObject();
            //获取赫夫曼编码表
            HashMap<Byte,String> huffmanCodes = (HashMap<Byte,String>)ois.readObject();

            //解压,获取解压后字节数组
            byte[] unZip = huffmanUnZip(zip, huffmanCodes);
            //创建文件输出流
            os = new FileOutputStream(dstFile);
            //将解压后字节数组,写入输出流,完成解压操作,恢复源文件
            os.write(unZip);

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

三、多叉树,多路查找树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. 2-3树

  1. 最简单的B树结构,特点如下(就是每个结点可以保存两个数据,最多可以有3个子结点,但是需要满足如下规则)
  1. 2-3树的所有叶子结点都在同一层.(只要是B树都满足这个条件)
  2. 有两个子结点的结点叫二结点,二结点要么没有子结点,要么有两个子结点
  3. 有三个子结点的结点叫三结点,三结点要么没有子结点,要么有3个结点
  4. 2-3树是由二结点和三结点构成的树
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
另外还有一些2-3-4树等等,和2-3树差不多,就是每个结点最多存3个元素,最多有4个子结点

在这里插入图片描述

2. B树,B+树,B*树

  1. B-tree,B树,Balanced-tree,平衡的意思,有人翻译成B-树,容易让人误认为叫B减树,或者是B杠树,和B树不一样。其实就是B树
    在这里插入图片描述
  2. B+树
    在这里插入图片描述
  3. B*树
    在这里插入图片描述
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殷丿grd_志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值