Java版数据结构和算法学习笔记之树结构篇

1. 树结构概述

1.1 什么是树结构

在这里插入图片描述

1.2 为什么使用树结构

查找和插入操作均比线性结构好。

1.3 树的基本概念

  1. 根节点是数据结构中,用来描述“树”型结构的名词。

    这种结构像一根倒着的树,每片树叶都长在一个结点上,这个结点就叫做这个叶子的父结点,这个叶子叫做你结点的子结点,没有子结点的结点叫叶子结点,没有父结点的结点叫根结点

  2. 双亲节点实际上就是父节点。

  3. 节点的度: 一个结点的子结点数。

  4. 节点的权:节点中存的数字。

  5. 子树

  6. 树的高度:最大的层数。

  7. 森林:森林(forest)是m(m≥0)棵互不相交的树的集合。任何一棵树,删除了根结点就变成了森林。

2. 二叉树

2.1 什么是二叉树

任何一个结点的子结点数量均不超过2。

二叉树的子结点分为左结点和右结点,且左结点与右结点不能随意颠倒位置。

满二叉树

所以叶子节点都在最后一层,且结点总数为2n-1,n为树的高度/层数。

完全二叉树

所有叶子节点都在最后一层或倒数第二层,且最后一层的叶子结点在左边连续,倒数第二次的叶子结点在右边连续。

注意:满二叉树实际上就是完全二叉树。没有任何结点的树是空树。

2.2 链式存储的二叉树

2.2.1 二叉树代码实现

package Tree;

public class TreeNode {
    //节点的权
    int value;
    //左结点
    TreeNode leftNode;
    //右结点
    TreeNode rightNode;
    public TreeNode(int value){
        this.value=value;
    }
    //设置左结点
    public void setLeftNode(TreeNode leftNode) {
        this.leftNode = leftNode;
    }
    //设置右结点
    public void setRightNode(TreeNode rightNode) {
        this.rightNode = rightNode;
    }

    public int getValue() {
        return value;
    }

    public TreeNode getLeftNode() {
        return leftNode;
    }

    public TreeNode getRightNode() {
        return rightNode;
    }
}

package Tree;

public class BinaryTree {
     TreeNode root;
     //设置根结点
    public void setRoot(TreeNode root) {
        this.root = root;
    }
    //获取根结点
    public TreeNode getRoot() {
        return root;
    }
}

package Tree;

public class TestBinaryTree {
    public static void main(String[] args) {
        //创建一棵树
        BinaryTree binaryTree=new BinaryTree();
        //创建一个根结点
        TreeNode root=new TreeNode(1);
        //把根节点赋给tree
        binaryTree.setRoot(root);
        //创建root的左结点
        TreeNode rootL=new TreeNode(2);
        root.setLeftNode(rootL);
        //创建root的右结点
        TreeNode rootR=new TreeNode(3);
        root.setRightNode(rootR);
        //输出根结点的值
        System.out.println(binaryTree.getRoot().getValue());
        //输出左结点的值
        System.out.println(root.getLeftNode().getValue());
        //输出左结点的值
        System.out.println(root.getRightNode().getValue());
    }
}

2.2.2 二叉树的遍历

  1. 前序遍历

    前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。

  2. 中序遍历

    中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。

  3. 后序遍历

    首先遍历左子树,然后遍历右子树,最后访问根结点。

  4. 例如:

在这里插入图片描述
前序遍历:GDAFEMHZ
中序遍历:ADEFGHMZ
后续遍历:AEFDHZMG

  1. 又如:

在这里插入图片描述

  1. 代码实现:主要是利用递归的思想
package Tree;

public class BinaryTree {
     TreeNode root;
     //设置根结点
    public void setRoot(TreeNode root) {
        this.root = root;
    }
    //获取根结点
    public TreeNode getRoot() {
        return root;
    }
    public void frontShow(){
        root.frontShow();
    }

    public void mediumShow(){
        root.mediumShow();
    }

    public void afterShow(){
        root.afterShow();
    }
}

package Tree;

public class TreeNode {
    //节点的权
    int value;
    //左结点
    TreeNode leftNode;
    //右结点
    TreeNode rightNode;
    public TreeNode(int value){
        this.value=value;
    }
    //设置左结点
    public void setLeftNode(TreeNode leftNode) {
        this.leftNode = leftNode;
    }
    //设置右结点
    public void setRightNode(TreeNode rightNode) {
        this.rightNode = rightNode;
    }

    public int getValue() {
        return value;
    }

    public TreeNode getLeftNode() {
        return leftNode;
    }

    public TreeNode getRightNode() {
        return rightNode;
    }

    public void frontShow(){
        System.out.print(this.value+" ");
        if(leftNode!=null) {
            leftNode.frontShow();
        }
        if(rightNode!=null) {
            rightNode.frontShow();
        }
    }

    public void mediumShow(){
        if(leftNode!=null) {
            leftNode.mediumShow();
        }
        System.out.print(this.value+" ");
        if(rightNode!=null) {
            rightNode.mediumShow();
        }
    }

    public void afterShow(){
        if(leftNode!=null) {
            leftNode.afterShow();
        }
        if(rightNode!=null) {
            rightNode.afterShow();
        }
        System.out.print(this.value+" ");
    }
}

package Tree;

public class TestBinaryTree {
    public static void main(String[] args) {
        //创建一棵树
        BinaryTree binaryTree=new BinaryTree();
        //创建一个根结点
        TreeNode root=new TreeNode(1);
        //把根节点赋给tree
        binaryTree.setRoot(root);
        //创建root的左结点
        TreeNode rootL=new TreeNode(2);
        root.setLeftNode(rootL);
        //创建root的右结点
        TreeNode rootR=new TreeNode(3);
        root.setRightNode(rootR);
        //为第二层的左结点创建两个子结点
        rootL.setLeftNode(new TreeNode(4));
        rootL.setRightNode(new TreeNode(5));
        //为第二层的右结点创建两个子结点
        rootR.setLeftNode(new TreeNode(6));
        rootR.setRightNode(new TreeNode(7));
        //树遍历
        System.out.print("前序遍历: ");
        binaryTree.frontShow();
        System.out.print("\n"+"中序遍历: ");
        binaryTree.mediumShow();
        System.out.print("\n"+"后序遍历: ");
        binaryTree.afterShow();
    }
}

在这里插入图片描述

2.2.3 二叉树的查找

以前序查找为例。

package Tree;

public class BinaryTree {
     TreeNode root;
     //设置根结点
    public void setRoot(TreeNode root) {
        this.root = root;
    }
    //获取根结点
    public TreeNode getRoot() {
        return root;
    }
    public void frontShow(){
        root.frontShow();
    }

    public void mediumShow(){
        root.mediumShow();
    }

    public void afterShow(){
        root.afterShow();
    }

    public TreeNode frontSearch(int i){
        return root.frontSearch(i);
    }
}

package Tree;

public class TestBinaryTree {
    public static void main(String[] args) {
        //创建一棵树
        BinaryTree binaryTree=new BinaryTree();
        //创建一个根结点
        TreeNode root=new TreeNode(1);
        //把根节点赋给tree
        binaryTree.setRoot(root);
        //创建root的左结点
        TreeNode rootL=new TreeNode(2);
        root.setLeftNode(rootL);
        //创建root的右结点
        TreeNode rootR=new TreeNode(3);
        root.setRightNode(rootR);
        //为第二层的左结点创建两个子结点
        rootL.setLeftNode(new TreeNode(4));
        rootL.setRightNode(new TreeNode(5));
        //为第二层的右结点创建两个子结点
        rootR.setLeftNode(new TreeNode(6));
        rootR.setRightNode(new TreeNode(7));
        //树遍历
        System.out.print("前序遍历: ");
        binaryTree.frontShow();
        System.out.print("\n"+"中序遍历: ");
        binaryTree.mediumShow();
        System.out.print("\n"+"后序遍历: ");
        binaryTree.afterShow();
        //前序查找
        TreeNode t=binaryTree.frontSearch(9);
        System.out.println(t);
    }
}

package Tree;

public class TreeNode {
    //节点的权
    int value;
    //左结点
    TreeNode leftNode;
    //右结点
    TreeNode rightNode;

    public TreeNode(int value) {
        this.value = value;
    }

    //设置左结点
    public void setLeftNode(TreeNode leftNode) {
        this.leftNode = leftNode;
    }

    //设置右结点
    public void setRightNode(TreeNode rightNode) {
        this.rightNode = rightNode;
    }

    public int getValue() {
        return value;
    }

    public TreeNode getLeftNode() {
        return leftNode;
    }

    public TreeNode getRightNode() {
        return rightNode;
    }

    public void frontShow() {
        System.out.print(this.value + " ");
        if (leftNode != null) {
            leftNode.frontShow();
        }
        if (rightNode != null) {
            rightNode.frontShow();
        }
    }

    public void mediumShow() {
        if (leftNode != null) {
            leftNode.mediumShow();
        }
        System.out.print(this.value + " ");
        if (rightNode != null) {
            rightNode.mediumShow();
        }
    }

    public void afterShow() {
        if (leftNode != null) {
            leftNode.afterShow();
        }
        if (rightNode != null) {
            rightNode.afterShow();
        }
        System.out.print(this.value + " ");
    }

    public TreeNode frontSearch(int i) {
        TreeNode target=null;
        if (this.value == i) {
            return this;
        } else {

            if (target==null&leftNode != null) {
                target=leftNode.frontSearch(i);
            }
            if (target==null&rightNode != null) {
                target=rightNode.frontSearch(i);
            }
        }
            return target;
        }
}

2.2.4 删除二叉树的子树

package Tree;

public class BinaryTree {
     TreeNode root;
     //设置根结点
    public void setRoot(TreeNode root) {
        this.root = root;
    }
    //获取根结点
    public TreeNode getRoot() {
        return root;
    }
    public void frontShow(){
        root.frontShow();
    }

    public void mediumShow(){
        root.mediumShow();
    }

    public void afterShow(){
        root.afterShow();
    }

    public TreeNode frontSearch(int i){
        return root.frontSearch(i);
    }

    public void delete(int i){
        if(root.value==i){
            root=null;
        }else{
            root.delete(i);
        }
    }
}

package Tree;

public class TestBinaryTree {
    public static void main(String[] args) {
        //创建一棵树
        BinaryTree binaryTree=new BinaryTree();
        //创建一个根结点
        TreeNode root=new TreeNode(1);
        //把根节点赋给tree
        binaryTree.setRoot(root);
        //创建root的左结点
        TreeNode rootL=new TreeNode(2);
        root.setLeftNode(rootL);
        //创建root的右结点
        TreeNode rootR=new TreeNode(3);
        root.setRightNode(rootR);
        //为第二层的左结点创建两个子结点
        rootL.setLeftNode(new TreeNode(4));
        rootL.setRightNode(new TreeNode(5));
        //为第二层的右结点创建两个子结点
        rootR.setLeftNode(new TreeNode(6));
        rootR.setRightNode(new TreeNode(7));
        //树遍历
        System.out.print("前序遍历: ");
        binaryTree.frontShow();
        System.out.print("\n"+"中序遍历: ");
        binaryTree.mediumShow();
        System.out.print("\n"+"后序遍历: ");
        binaryTree.afterShow();
        //前序查找
        TreeNode t=binaryTree.frontSearch(3);
        System.out.println(t);
        //删除子树
        binaryTree.delete(5);
        binaryTree.frontShow();
        //前序查找
        TreeNode t1=binaryTree.frontSearch(3);
        System.out.println(t1);
    }
}

package Tree;

public class TreeNode {
    //节点的权
    int value;
    //左结点
    TreeNode leftNode;
    //右结点
    TreeNode rightNode;

    public TreeNode(int value) {
        this.value = value;
    }

    //设置左结点
    public void setLeftNode(TreeNode leftNode) {
        this.leftNode = leftNode;
    }

    //设置右结点
    public void setRightNode(TreeNode rightNode) {
        this.rightNode = rightNode;
    }

    public int getValue() {
        return value;
    }

    public TreeNode getLeftNode() {
        return leftNode;
    }

    public TreeNode getRightNode() {
        return rightNode;
    }

    public void frontShow() {
        System.out.print(this.value + " ");
        if (leftNode != null) {
            leftNode.frontShow();
        }
        if (rightNode != null) {
            rightNode.frontShow();
        }
    }

    public void mediumShow() {
        if (leftNode != null) {
            leftNode.mediumShow();
        }
        System.out.print(this.value + " ");
        if (rightNode != null) {
            rightNode.mediumShow();
        }
    }

    public void afterShow() {
        if (leftNode != null) {
            leftNode.afterShow();
        }
        if (rightNode != null) {
            rightNode.afterShow();
        }
        System.out.print(this.value + " ");
    }

    public TreeNode frontSearch(int i) {
        TreeNode target=null;
        if (this.value == i) {
            return this;
        } else {

            if (target==null&leftNode != null) {
                target=leftNode.frontSearch(i);
            }
            if (target==null&rightNode != null) {
                target=rightNode.frontSearch(i);
            }
        }
            return target;
        }

        public void delete(int i){
        TreeNode parent=this;
        if(parent.leftNode!=null&&parent.leftNode.value==i){
            this.leftNode=null;
            return;
        }
        if(parent.rightNode!=null&&parent.rightNode.value==i){
            this.rightNode=null;
            return;
        }
        parent=leftNode;
        if(parent!=null){
            parent.delete(i);
        }
        parent=rightNode;
        if (parent!=null){
            parent.delete(i);
        }
        }
}

2.2.5 部分代码改进

package Tree;

public class BinaryTree {
     TreeNode root;
     //设置根结点
    public void setRoot(TreeNode root) {
        this.root = root;
    }
    //获取根结点
    public TreeNode getRoot() {
        return root;
    }
    public void frontShow(){
        if(root!=null){
            root.frontShow();  
        }
    }

    public void mediumShow() {
        if (root != null) {
            root.mediumShow();
        }
    }

    public void afterShow() {
        if (root != null) {
            root.afterShow();
        }
    }
    
    public TreeNode frontSearch(int i){
        return root.frontSearch(i);
    }

    public void delete(int i){
        if(root.value==i){
            root=null;
        }else{
            root.delete(i);
        }
    }
}

优秀博客1
优秀博客2

2.3 顺序存储的二叉树

2.3.1 基本概念

顺序存储的二叉树通常情况只考虑完全二叉树。

  1. 第n个结点的左子结点是2*n+1
  2. 第n个结点的右子结点是2*n+2
  3. 第n个结点的父结点是(n-1)/2

2.3.2 顺序二叉树的遍历

package ArrayTree;

public class ArrayBinaryTree {
    int[] data;
    public ArrayBinaryTree(int[] data){
        this.data=data;
    }

    public void frontShow(int start){
        if(this.data==null||data.length==0){
            return;
        }
        //先遍历当前内容
        System.out.print(data[start]+" ");
        //再遍历左结点
        if(2*start+1<data.length) {
            frontShow(2 * start + 1);
        }
        //再遍历右结点
        if(2*start+2<data.length) {
            frontShow(2 * start + 2);
        }
    }
}

package ArrayTree;

public class TestAraayBinaryTree {
    public static void main(String[] args) {
        int[] data={1,2,3,4,5,6,7};
        ArrayBinaryTree tree=new ArrayBinaryTree(data);
        tree.frontShow(0);
    }
}

3. 线索二叉树

3.1 基本概念

百度百科之线索二叉树

  1. 线索化二叉树时,一个结点的前一个结点叫前驱结点。
  2. 线索化二叉树时,一个结点的后一个结点叫后继结点。

3.2 线索二叉树的实现

package ThreadedTree;

public class TestThreadedBinaryTree {
    public static void main(String[] args) {
        //创建一棵树
        ThreadedBinaryTree binaryTree=new ThreadedBinaryTree();
        //创建一个根结点
        ThreadedtreeNode root=new ThreadedtreeNode(1);
        //把根节点赋给tree
        binaryTree.setRoot(root);
        //创建root的左结点
        ThreadedtreeNode rootL=new ThreadedtreeNode(2);
        root.setLeftNode(rootL);
        //创建root的右结点
        ThreadedtreeNode rootR=new ThreadedtreeNode(3);
        root.setRightNode(rootR);
        //为第二层的左结点创建两个子结点
        rootL.setLeftNode(new ThreadedtreeNode(4));
        ThreadedtreeNode fiveNode=new ThreadedtreeNode(5);
        rootL.setRightNode(fiveNode);
        //为第二层的右结点创建两个子结点
        rootR.setLeftNode(new ThreadedtreeNode(6));
        rootR.setRightNode(new ThreadedtreeNode(7));
        //树遍历
        System.out.print("前序遍历: ");
        binaryTree.frontShow();
        System.out.print("\n"+"中序遍历: ");
        binaryTree.mediumShow();
        System.out.print("\n"+"后序遍历: ");
        binaryTree.afterShow();
      //中序线索化二叉树
        binaryTree.threadedNodes();
        System.out.println("验证中序线索二叉树功能:");
        System.out.println(fiveNode.leftNode.value);
        System.out.println(fiveNode.leftNode.leftType);
    }
}

package ThreadedTree;

public class ThreadedBinaryTree {
     ThreadedtreeNode root;
     //前驱结点
     ThreadedtreeNode pre;
     //设置根结点
    public void setRoot(ThreadedtreeNode root) {
        this.root = root;
    }

    public void threadedNodes(){
        threadedNodes(root);
    }
    //中序线索化二叉树
    public void threadedNodes(ThreadedtreeNode node){
        if(node==null){
            return;
        }
        //处理左子树
        threadedNodes(node.leftNode);
        //处理前驱结点
        if(node.leftNode==null){
            node.leftNode=pre;
            node.leftType=1;
        }
        if(pre!=null&&pre.rightNode==null){
            pre.rightNode=node;
            pre.rightType=1;
        }
        pre=node;
        //处理右子树
        threadedNodes(node.rightNode);
    }
    //获取根结点
    public ThreadedtreeNode getRoot() {
        return root;
    }
    public void frontShow(){
        if(root!=null){
            root.frontShow();
        }
    }

    public void mediumShow() {
        if (root != null) {
            root.mediumShow();
        }
    }

    public void afterShow() {
        if (root != null) {
            root.afterShow();
        }
    }

    public ThreadedtreeNode frontSearch(int i){
        return root.frontSearch(i);
    }

    public void delete(int i){
        if(root.value==i){
            root=null;
        }else{
            root.delete(i);
        }
    }
}

package ThreadedTree;

public class ThreadedtreeNode {
    //节点的权
    int value;
    //左指针
    ThreadedtreeNode leftNode;
    //右指针
    ThreadedtreeNode rightNode;
    //标识指针类型
    int leftType;
    int rightType;

    public ThreadedtreeNode(int value) {
        this.value = value;
    }

    //设置左结点
    public void setLeftNode(ThreadedtreeNode leftNode) {
        this.leftNode = leftNode;
    }

    //设置右结点
    public void setRightNode(ThreadedtreeNode rightNode) {
        this.rightNode = rightNode;
    }

    public int getValue() {
        return value;
    }

    public ThreadedtreeNode getLeftNode() {
        return leftNode;
    }

    public ThreadedtreeNode getRightNode() {
        return rightNode;
    }

    public void frontShow() {
        System.out.print(this.value + " ");
        if (leftNode != null) {
            leftNode.frontShow();
        }
        if (rightNode != null) {
            rightNode.frontShow();
        }
    }

    public void mediumShow() {
        if (leftNode != null) {
            leftNode.mediumShow();
        }
        System.out.print(this.value + " ");
        if (rightNode != null) {
            rightNode.mediumShow();
        }
    }

    public void afterShow() {
        if (leftNode != null) {
            leftNode.afterShow();
        }
        if (rightNode != null) {
            rightNode.afterShow();
        }
        System.out.print(this.value + " ");
    }

    public ThreadedtreeNode frontSearch(int i) {
        ThreadedtreeNode target=null;
        if (this.value == i) {
            return this;
        } else {

            if (target==null&leftNode != null) {
                target=leftNode.frontSearch(i);
            }
            if (target==null&rightNode != null) {
                target=rightNode.frontSearch(i);
            }
        }
            return target;
        }

        public void delete(int i){
        ThreadedtreeNode parent=this;
        if(parent.leftNode!=null&&parent.leftNode.value==i){
            this.leftNode=null;
            return;
        }
        if(parent.rightNode!=null&&parent.rightNode.value==i){
            this.rightNode=null;
            return;
        }
        parent=leftNode;
        if(parent!=null){
            parent.delete(i);
        }
        parent=rightNode;
        if (parent!=null){
            parent.delete(i);
        }
        }
}

3.3 二叉树的遍历

public void threadedIterate(){
        ThreadedtreeNode node=root;
        while (node!=null){
            while (node.leftType==0){
                node=node.leftNode;
            }
            System.out.println(node.value);
            while (node.rightType==1) {
                node=node.rightNode;
                System.out.println(node.value);
            }
            node = node.rightNode;
        }
    }

4. 赫夫曼树

4.1 什么是赫夫曼树

  1. 最优二叉树

    它是n个带权叶子结点构成的所有二叉树中,带权路径长度最小的二叉树。

  2. 叶结点的带权路径

    从根节点到叶结点经过的结点数x叶子结点的权重

  3. 树的带权路径长度

    所有叶子结点的带权路径和

优秀博客

4.2 赫夫曼树的创建

package hefuman;

public class Node implements Comparable<Node> {
    int value;
    Node left;
    Node right;

    public Node(int value){
        this.value=value;
    }

    @Override
    public int compareTo(Node o) {
        return this.value-o.value;
    }
}

package hefuman;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        int[] arr = {3, 7, 8, 29, 5, 11, 23, 14};
        Node node = creatHefumanTree(arr);
    }

    public static Node creatHefumanTree(int[] arr) {
        //先使用数组中所有的元素创建若干个二叉树(只有一个结点)
        List<Node> nodes = new ArrayList<>();
        for (int value : arr) {
            nodes.add(new Node(value));
        }
        //循环处理
        while (nodes.size() > 1) {
            //排序
            Collections.sort(nodes);
            //取出权值最小的两个二叉树
            Node left = nodes.get(0);
            Node right = nodes.get(1);
            //创建新的二叉树
            Node parent = new Node(left.value + right.value);
            parent.left=left;
            parent.right=right;
            //把取出的二叉树移除
            nodes.remove(left);
            nodes.remove(right);
            //放入原来的二叉树集合中
            nodes.add(parent);
        }
        System.out.println(nodes);
        return null;
    }
}

4.3 赫夫曼编码

package hufumanCode;

public class Node implements Comparable<Node> {
    Byte data;
    int weight;
    Node left;
    Node right;
    public Node(Byte data,int weight){
        this.data=data;
        this.weight=weight;
    }

    @Override
    public int compareTo(Node o) {
        return this.weight-o.weight;
    }
}

package· hufumanCode;

import java.util.*;

public class TestHufumanCode {
    public static void main(String[] args) {
        String msg = "can you can a can as a can canner can a can.";
        byte[] bytes = msg.getBytes();
        //进行赫夫曼编码
        hufumanZip(bytes);
    }

    private static byte[] hufumanZip(byte[] bytes) {
        //统计每一个byte出现的次数
        List<Node> nodes = getNodes(bytes);
        //创建一棵赫夫曼树
        Node tree = createHufumanTree(nodes);
        //System.out.println(tree.weight);
        //创建一个赫夫曼编码表
        Map<Byte, String> huffCodes = getCodes(tree);
        //编码
        //System.out.println(huffCodes);
        //进行赫夫曼编码压缩
        byte[] b=zip(bytes,huffCodes);
        System.out.println(bytes.length);
        System.out.println(b.length);
        return null;
    }

    /**
     * 把byte数组转为node集合
     *
     * @param bytes
     * @return
     */
    private static List<Node> getNodes(byte[] bytes) {
        List<Node> nodes = new ArrayList<>();
        Map<Byte, Integer> counts = new HashMap<>();
        //统计每一个byte出现的次数
        for (byte b : bytes) {
            Integer count = counts.get(b);
            if (count == null) {
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

    /**
     * 创建赫夫曼树
     */
    private static Node createHufumanTree(List<Node> nodes) {
        while (nodes.size() > 1) {
            //排序
            Collections.sort(nodes);
            //取出权值最小的两棵树
            Node left = nodes.get(0);
            Node right = nodes.get(1);
            //创建新的树
            Node parent = new Node(null, left.weight + right.weight);
            parent.left=left;
            parent.right=right;
            //移除两颗权值最小的树
            nodes.remove(left);
            nodes.remove(right);
            //增加新的树
            nodes.add(parent);
        }
        return nodes.get(0);
    }

    static StringBuilder sb = new StringBuilder();
    static Map<Byte, String> huffCodes = new HashMap<>();

    private static Map<Byte, String> getCodes(Node tree) {
        if (tree == null) {
            return null;
        }
        getCodes(tree.left, "0", sb);
        getCodes(tree.right, "1", sb);
        return huffCodes;
    }

    private static void getCodes(Node node, String code, StringBuilder sb) {
        StringBuilder sb2 = new StringBuilder(sb);
        sb2.append(code);
        //不是叶结点
        if (node.data == null) {
            getCodes(node.left, "0", sb2);
            getCodes(node.right, "1", sb2);
        } else {//如果是叶结点
            huffCodes.put(node.data, sb2.toString());
        }
    }

    /**
     * 进行赫夫曼编码压缩
     * @param bytes
     * @param huffCodes
     * @return
     */
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffCodes){
        StringBuilder sb=new StringBuilder();
        for (byte b:bytes){
            sb.append(huffCodes.get(b));
        }
        System.out.println(sb.toString());
        //定义长度
        int len;
        if(sb.length()%8==0){
            len=sb.length()/8;
        }else{
            len=sb.length()/8+1;
        }
        byte[] by=new byte[len];
        int index=0;
        for(int i=0;i<sb.length();i+=8){
            String strByte;
            if(i+8<=sb.length()){
                strByte=sb.substring(i,i+8);
            }else{
                strByte=sb.substring(i,sb.length()-1);
            }
            Byte byt=(byte)Integer.parseInt(strByte,2);
            by[index]=byt;
            index++;
        }
        return by;
    }
}

4.4 赫夫曼译码

package hufumanCode;

import java.util.*;

public class TestHufumanCode {
    public static void main(String[] args) {
        String msg = "yang ting ting is testing her javacode of hufuman.";
        byte[] bytes = msg.getBytes();
        //进行赫夫曼编码压缩
        byte[] b=hufumanZip(bytes);
        //使用赫夫曼编码进行解码
        byte[] newBytes=decode(huffCodes,b);
        //验证
        System.out.println(Arrays.toString(bytes));
        System.out.println(Arrays.toString(newBytes));
        //
        System.out.println(new String(newBytes));
    }

    private static byte[] hufumanZip(byte[] bytes) {
        //统计每一个byte出现的次数
        List<Node> nodes = getNodes(bytes);
        //创建一棵赫夫曼树
        Node tree = createHufumanTree(nodes);
        //System.out.println(tree.weight);
        //创建一个赫夫曼编码表
        Map<Byte, String> huffCodes = getCodes(tree);
        //编码
        //System.out.println(huffCodes);
        //进行赫夫曼编码压缩
        byte[] bb=zip(bytes,huffCodes);
        return bb;
    }

    /**
     * 把byte数组转为node集合
     *
     * @param bytes
     * @return
     */
    private static List<Node> getNodes(byte[] bytes) {
        List<Node> nodes = new ArrayList<>();
        Map<Byte, Integer> counts = new HashMap<>();
        //统计每一个byte出现的次数
        for (byte b : bytes) {
            Integer count = counts.get(b);
            if (count == null) {
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

    /**
     * 创建赫夫曼树
     */
    private static Node createHufumanTree(List<Node> nodes) {
        while (nodes.size() > 1) {
            //排序
            Collections.sort(nodes);
            //取出权值最小的两棵树
            Node left = nodes.get(0);
            Node right = nodes.get(1);
            //创建新的树
            Node parent = new Node(null, left.weight + right.weight);
            parent.left=left;
            parent.right=right;
            //移除两颗权值最小的树
            nodes.remove(left);
            nodes.remove(right);
            //增加新的树
            nodes.add(parent);
        }
        return nodes.get(0);
    }

    static StringBuilder sb = new StringBuilder();
    static Map<Byte, String> huffCodes = new HashMap<>();

    private static Map<Byte, String> getCodes(Node tree) {
        if (tree == null) {
            return null;
        }
        getCodes(tree.left, "0", sb);
        getCodes(tree.right, "1", sb);
        return huffCodes;
    }

    private static void getCodes(Node node, String code, StringBuilder sb) {
        StringBuilder sb2 = new StringBuilder(sb);
        sb2.append(code);
        //不是叶结点
        if (node.data == null) {
            getCodes(node.left, "0", sb2);
            getCodes(node.right, "1", sb2);
        } else {//如果是叶结点
            huffCodes.put(node.data, sb2.toString());
        }
    }

    /**
     * 进行赫夫曼编码压缩
     * @param bytes
     * @param huffCodes
     * @return
     */
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffCodes){
        StringBuilder sb=new StringBuilder();
        for (byte b:bytes){
            sb.append(huffCodes.get(b));
        }
       // System.out.println(sb.toString());
        //定义长度
        int len;
        if(sb.length()%8==0){
            len=sb.length()/8;
        }else{
            len=sb.length()/8+1;
        }
        byte[] by=new byte[len];
        int index=0;
        for(int i=0;i<sb.length();i+=8){
            String strByte;
            if(i+8<=sb.length()){
                strByte=sb.substring(i,i+8);
            }else{
                strByte=sb.substring(i,sb.length());
            }
            Byte byt=(byte)Integer.parseInt(strByte,2);
            //System.out.print(byt);
            by[index]=byt;
            index++;
        }
        return by;
    }

    /**
     * 赫夫曼解码
     * @param huffCodes
     * @return
     */
    private static byte[] decode(Map<Byte,String> huffCodes,byte[] bytes){
        StringBuilder sb=new StringBuilder();
        //把byte数组转化为二进制的字符串
        for (int i=0;i<bytes.length;i++){
            byte b=bytes[i];
            boolean flag=(i==bytes.length-1);
            sb.append(byteToBitStr(!flag,b));
        }
       //System.out.println(sb.toString());
        //把赫夫曼编码的键值对进行调换
        Map<String,Byte> map=new HashMap<>();
        for (Map.Entry<Byte,String> entry:huffCodes.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }
        List<Byte> list=new ArrayList<>();
        //处理字符串
        for (int i=0;i<sb.length();){
            int count=1;
            boolean flag=true;
            Byte b=null;
            while (flag){
                String key=sb.substring(i,i+count);
                b=map.get(key);
                if(b==null){
                    count++;
                }else {
                    flag=false;
                }
            }
            list.add(b);
            i+=count;
        }
        //把集合转为数组
        byte[] b=new byte[list.size()];
        for (int i=0;i<list.size();i++){
            b[i]=list.get(i);
        }

        return b;
    }

    private static String byteToBitStr(boolean flag,byte b){
        int temp=b;
        if(flag) {
            temp |= 256;
        }
        String str=Integer.toBinaryString(temp);
        if(flag){
            return  str.substring(str.length()-8);
        }
        return str;
    }
}
package hufumanCode;

public class Node implements Comparable<Node> {
    Byte data;
    int weight;
    Node left;
    Node right;
    public Node(Byte data,int weight){
        this.data=data;
        this.weight=weight;
    }

    @Override
    public int compareTo(Node o) {
        return this.weight-o.weight;
    }
}

在这里插入图片描述

5. 二叉排序树(二叉查找树)

5.1 概述

对于二叉树中的任何一个非叶子结点,都要求左子结点比当前节点值小,而右子结点比当前结点值大。

注意:如果是一棵空树,我们也可以认为它是二叉排序树。

5.2 代码实现

  1. 树的创建
  2. 树的中序遍历
  3. 指定值查找元素
  4. 删除结点
package BinarySortTree;

public class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    public void add(Node 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);
            }
        }
    }

    public void mediumShow() {
        if (this.left != null) {
            this.left.mediumShow();
        }
        System.out.print(this.value + " ");
        if (this.right != null) {
            this.right.mediumShow();
        }
    }

    public Node search(int value) {
        if (this.value == value) {
            return this;
        }
        if (this.right!= null && this.value < value) {
            return this.right.search(value);
        }
        if (this.left != null && this.value > value) {
            return this.left.search(value);
        }
            return null;
        }



    public Node searchParent(int value) {
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
            return this;
        } else {
            if (this.value > value && this.left != null) {
                return this.left.searchParent(value);
            }
            if (this.value < value && this.right != null) {
                return this.right.searchParent(value);
            }
        }
        return null;
    }
}


package BinarySortTree;

public class BinarySortTree {
    Node root;

    public void add(Node node){
        if(root==null){
            root=node;
        }else{
            root.add(node);
        }
    }

    public void mediumShow(){
        if(root==null){
            return;
        }else{
            root.mediumShow();
        }
    }

    public Node search(int value){
        if(root==null){
            return null;
        }else{
            return root.search(value);
        }
    }

    public void delete(int value){
        if(root==null){
            return;
        }else{
            Node target=search(value);
            if(target==null){
                return;
            }
            //找到他的父结点
            Node parent=searchParent(value);
            //删除的结点是叶子结点
            if(target.left==null&&target.right==null){
                if(target.value<parent.value){
                    parent.left=null;
                }else{
                    parent.right=null;
                }
            }
            //删除的结点只有一个子结点
            if(target.left==null&&target.right!=null){
                if(target.value<parent.value){
                    parent.left=target.right;
                }else{
                    parent.right=target.right;
                }
            }
            if(target.left!=null&&target.right==null){
                if(target.value<parent.value){
                    parent.left=target.left;
                }else{
                    parent.right=target.left;
                }
            }
            //删除的结点有两个子结点
            if(target.left!=null&&target.right!=null){
              //删除右子树中值最小的结点,并拿到它的值
                int min=deleteMin(target.right);
                //替换目标值
                target.value=min;
            }
        }
    }

    /**
     * 查找父结点,根结点的父结点不处理
     * @param value
     * @return
     */
    public Node searchParent(int value){
        if( root==null){
            return null;
        }
        return root.searchParent(value);
    }

    /**
     * 删除右子树中最小的结点
     * @param node
     * @return
     */
    public int deleteMin(Node node){
        int cur=node.value;
        if(node.left!=null){
            return deleteMin(node.left);
        }else{
            delete(cur);
        }
            return cur;
        }
    }


package BinarySortTree;

public class Test {
    public static void main(String[] args) {
        int[] arr={7,3,10,1,12,5,9};
        BinarySortTree tree=new BinarySortTree();
        //创建一颗二叉树
        for(int i=0;i<arr.length;i++){
           tree.add(new Node(arr[i]));
        }
        tree.mediumShow();
        System.out.println("\n"+"-----------------");
        Node node=tree.search(3);
        System.out.println(node);
        System.out.println(tree.searchParent(5));
        tree.delete(3);
        tree.mediumShow();
    }
}

6. AVL树—平衡二叉树

6.1 为什么要用平衡二叉树

二叉搜索树有一个缺点,在插入数据是有序的序列(包括升序和降序),会导致二叉树退化成链表,从而导致在查找,删除,添加时的性能均从O(logN)降低为O(N),这是不能接受的。

比如:

在这里插入图片描述
究其原因,是因为二叉搜索树退化成链表的时候,树的高度与节点的个数相等,也就是成正比,所以为了优化这种情况,就出现了具有平衡能力的二叉搜索树,其中AVL树是最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1, 因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logN)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。

6.2 平衡二叉树定义

  1. 对于任何一个结点而言,左子树和右子树的高度差的绝对值不大于1。
  2. 左子树和右子树也是平衡二叉树。

B树、红黑树都是平衡二叉树。

6.3 平衡二叉树关键问题

6.3.1 如何求树的高度

在这里插入图片描述

代码实现

public int height(){
        return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
    }

6.3.2 关于树的旋转

右旋转:

在这里插入图片描述

右旋转代码实现

  1. 创建一个新结点,结点值为当前结点的值
  2. 把新结点的右子树设置为当前结点的右子树
  3. 把新结点的左子树设置为当前结点的左子树的右子树
  4. 将当前结点的值变为左子结点的值
  5. 把当前结点的左子树设置为左子结点的左子树
  6. 将新结点作为当前结点的右子树
 if (left!=null&&right!=null&&left.height()-right.height()>=2){
            //进行右旋转
            rightRotate();
        }
//右旋转
    public void rightRotate(){
        //创建一个新结点,结点值为当前结点的值
        Node newNode=new Node(value);
        //把新结点的右子树设置为当前结点的右子树
        newNode.right=right;
        //把新结点的左子树设置为当前结点的左子树的右子树
        newNode.left=left.right;
        //将当前结点的值变为左子结点的值
        value=left.value;
        //把当前结点的左子树设置为左子结点的左子树
        left=left.left;
        //将新结点作为当前结点的右子树
        right=newNode;
    }

左旋转:
在这里插入图片描述
左旋转代码实现

  1. 创造一个新结点,结点值为当前结点值
  2. 将当前结点的左子树设为新结点的左子树
  3. 将当前结点的右子结点的左子树设置为新结点的右子树
  4. 将当前结点值设置为当前结点的右子结点的值
  5. 将当前结点的右子树设置为当前结点的右子结点的右子树
  6. 将新结点所在的树设置为当前结点的左子树
        if (left != null && right != null && right.height() - left.height() >= 2) {
            //进行左旋转
            leftRotate();
        }
//左旋转
    public void leftRotate(){
        //创造一个新结点,结点值为当前结点值
        Node newNode=new Node(value);
        //将当前结点的左子树设为新结点的左子树
        newNode.left=left;
        //将当前结点的右子结点的左子树设置为新结点的右子树
        newNode.right=right.left;
        //将当前结点值设置为当前结点的右子结点的值
        value=right.value;
        //将当前结点的右子树设置为当前结点的右子结点的右子树
        right=right.right;
        //将新结点所在的树设置为当前结点的左子树
        left=newNode;
    }

双旋转

在这里插入图片描述

6.3.3 小结

在这里插入图片描述

6.4 平衡二叉树实现代码

package AvlTree;

public class AvlTree {
    Node root;

    public void add(Node node){
        if(root==null){
            root=node;
        }else{
            root.add(node);
        }
    }

    public void mediumShow(){
        if(root==null){
            return;
        }else{
            root.mediumShow();
        }
    }

    public Node search(int value){
        if(root==null){
            return null;
        }else{
            return root.search(value);
        }
    }

    /**
     * 查找父结点,根结点的父结点不处理
     * @param value
     * @return
     */
    public Node searchParent(int value){
        if( root==null){
            return null;
        }
        return root.searchParent(value);
    }
    }


package AvlTree;

public class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    public void add(Node 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);
            }
        }

        //查询是否平衡
        if (left != null && right != null && left.height() - right.height() >= 2) {
            //进行右旋转
            //先左旋后右旋
            if(left.left!=null&left.right!=null&&left.left.height()<left.right.height()){
                left.leftRotate();
            }
            rightRotate();
        }
        if (left != null && right != null && right.height() - left.height() >= 2) {
            //进行左旋转
            //先右后左
            if(right.left!=null&right.right!=null&&right.right.height()<right.left.height()) {
                right.rightRotate();
            }
            leftRotate();
        }
    }

    public void mediumShow() {
        if (this.left != null) {
            this.left.mediumShow();
        }
        System.out.print(this.value + " ");
        if (this.right != null) {
            this.right.mediumShow();
        }
    }

    public Node search(int value) {
        if (this.value == value) {
            return this;
        }
        if (this.right!= null && this.value < value) {
            return this.right.search(value);
        }
        if (this.left != null && this.value > value) {
            return this.left.search(value);
        }
            return null;
        }



    public Node searchParent(int value) {
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
            return this;
        } else {
            if (this.value > value && this.left != null) {
                return this.left.searchParent(value);
            }
            if (this.value < value && this.right != null) {
                return this.right.searchParent(value);
            }
        }
        return null;
    }
    //求树的高度
    public int height(){
        return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
    }

    //右旋转
    public void rightRotate(){
        //创建一个新结点,结点值为当前结点的值
        Node newNode=new Node(value);
        //把新结点的右子树设置为当前结点的右子树
        newNode.right=right;
        //把新结点的左子树设置为当前结点的左子树的右子树
        newNode.left=left.right;
        //将当前结点的值变为左子结点的值
        value=left.value;
        //把当前结点的左子树设置为左子结点的左子树
        left=left.left;
        //将新结点作为当前结点的右子树
        right=newNode;
    }

    //左旋转
    public void leftRotate(){
        //创造一个新结点,结点值为当前结点值
        Node newNode=new Node(value);
        //将当前结点的左子树设为新结点的左子树
        newNode.left=left;
        //将当前结点的右子结点的左子树设置为新结点的右子树
        newNode.right=right.left;
        //将当前结点值设置为当前结点的右子结点的值
        value=right.value;
        //将当前结点的右子树设置为当前结点的右子结点的右子树
        right=right.right;
        //将新结点所在的树设置为当前结点的左子树
        left=newNode;
    }
}


package AvlTree;

public class Test {
    public static void main(String[] args) {
        int[] arr={5,4,8,7,9,6};
        AvlTree tree=new AvlTree();
        //创建一颗二叉树
        for(int i=0;i<arr.length;i++){
           tree.add(new Node(arr[i]));
        }
        System.out.println(tree.root.height());
        System.out.println(tree.root.value);
    }
}

7. 多路查找树

增加每个结点存放元素的个数,以减少树的高度,提高计算机访问硬盘速度。

7.1 计算机存储方式

7.1.1硬盘

  1. 机械硬盘
    在这里插入图片描述

    数据存在一个一个的扇区中。
    优点: 造价低,容量大,断电后不容易丢失。
    缺点: 由于存储介质的特性,再加上机械运动耗费时间,所以磁盘的速度较慢。

  2. 固态硬盘

在这里插入图片描述
3. 两者区别

固态硬盘跟机械硬盘都是怎么储存数据的

揭开固态硬盘的神秘面纱,一张图看懂机械硬盘和固态硬盘的区别!

硬盘结构(机械硬盘和固态硬盘)详解

7.1.2 内存

优点: 使用电信号来保存信息,不存在机器操作,所以访问速度非常快。
缺点: 造价高,断电后数据容易丢失,一般作为CPU的高速缓存。

7.2 2-3树—B树的一种特例

  1. B树中所有的叶子结点都在同一层。
  2. 有两个子结点的结点叫二结点,有三个子结点的结点叫三结点。
  3. 二结点要么有两个子结点,要么没有子结点;三结点要么有三个子结点,要么没有子结点。

在这里插入图片描述

7.3 2-3-4树

  1. B树中所有的叶子结点都在同一层。
  2. 有两个子结点的结点叫二结点,有三个子结点的结点叫三结点,有四个子结点的结点叫四结点。
  3. 二结点要么有两个子结点,要么没有子结点;三结点要么有三个子结点,要么没有子结点;四结点要么有四个子结点,要么没有子结点。

7.4 B树

  1. B树的阶
  2. B+树:非叶子结点只存储索引信息,不存储数据;叶子结点最右边的指针指向下一个相邻的叶结点。
  3. 所有的叶结点组成了一个有序链表。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

就是二二二二婷

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

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

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

打赏作者

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

抵扣说明:

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

余额充值