Huffman编码和解码

一.Huffman树

定义:  给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径达到最小,这样的二叉树称为最优二叉树,也称为霍夫曼树(Huffman树).

特点:     Huffman树是带权路径长度最短的树,权值较大的节点离根节点较近

    权值 = 当前节点的值 * 层数,wpl最小的值,就是Huffman树

创建步骤  举例  {13,7,8,3,29,6,1}

    1.从小到大进行排序,将每一个数据视为一个节点,每一个节点都可视为一个二叉树

    2.取出根节点权值两个最小的二叉树

    3.组成一个新的二叉树,新的二叉树根节点的权值是前面两颗二叉树节点权值之和

    4.再将这颗二叉树以根节点的权值大小进行再排序,不断重复1,2,3,4步,直到所有的数据都被处理,就得到一个Huffman树

class Node implements Comparable<Node> {
   // 实现Comparable接口,可以使用Collections工具类进行排序
    public int value;
    public Node left;
    public Node right;

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

    public Node() {
    }

    /*用于测试Huffman树是否正确*/
    public void preOrder(){
        System.out.println(this);
        if (this.left != null){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                ‘}‘;
    }

    @Override
    public int compareTo(Node o) { // 从小到大进行排序
        return this.value - o.value;
    }
}
 /**
     * 创建霍夫曼树
     * @param array 原数组
     * @return 创建好Huffman树的root节点
     */
    public static Node createHuffmanTree(int[] array){
        if (array.length == 0 || array == null){
            System.out.println("数组为空,无法创建");
            return null;
        }
        /*遍历数组中的每一个元素,构造成Node,放入到List中*/
        List<Node> nodes = new ArrayList<>();
        for (int item : array) {
            nodes.add(new Node(item));
        }

        while (nodes.size() > 1){ /*只要List中有元素,就一直进行权值计算*/
            /*对Node进行排序*/
            Collections.sort(nodes);

            /*取出根节点两个权值最小的二叉树*/
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);

            /*构建一个新的二叉树*/
            Node parent = new Node(leftNode.value + rightNode.value);
            parent.left = leftNode;
            parent.right = rightNode;

            /*从List中删除使用过的节点*/
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            /*将新的节点加入到List中*/
            nodes.add(parent);
        }
        /*返回Huffman树的root节点*/
        return nodes.get(0);
    }
}    

测试,如果生成的Huffman树是正确的,那么前序遍历的结果也是正确的

public static void main(String[] args) {
        int[] array = {13,7,8,3,29,6,1};
        preOrder(createHuffmanTree(array));
    }

    public static void preOrder(Node root){
        if (root != null){
            root.preOrder();
        }else {
            System.out.println("该树为空,不能遍历");
            return;
        }
    }

二.Huffman编码

定义:    Huffman编码是一种通信的编码,是在电通信领域的基本编码之一

作用:  Huffman编码广泛的应用于数据文件的压缩,而且它是前缀编码,可以有效的节省传输的带宽

编码的步骤:     举例  String content = ‘i like like like java do you like a java oh oh oh‘;

  1.生成节点   

/*定义节点,data用于存放数据,weight用于存放权值*/
class HuffmanNode implements Comparable<HuffmanNode>{
    public Byte data;
    public int weight;
    public HuffmanNode left;
    public HuffmanNode right;

    public HuffmanNode(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    public HuffmanNode() {
    }

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

}

  2.统计字符串中每一个字符出现的次数

/*统计字符串中每个字符出现的次数,放在List中进行返回*/
/*List存储格式 [Node[date=97 ,weight = 5], Node[date=32,weight = 9]......]*/
public static List<HuffmanNode> getNodes(byte[] bytes){
        if (bytes.length == 0 || bytes == null){
            System.out.println("字符串为空,无法进行编码");
            return null;
        }
        List<HuffmanNode> nodes = new ArrayList<>();
        Map<Byte,Integer> counts = new HashMap<>();
        /*遍历bytes ,统计每一个byte出现的次数*/
        for (byte item : bytes) {
           Integer count = counts.get(item);
           if (count == null){ // Map中没有这个字符,说明是第一次
               counts.put(item,1);
           }else {
               counts.put(item,count+1);
           }
        }
         /*遍历Map,将键值对转换为Node对象进行存放到List中*/
        for (Map.Entry<Byte,Integer> node:counts.entrySet()){
            nodes.add(new HuffmanNode(node.getKey(),node.getValue()));
        }
        return nodes;
    }

  3.根据List集合,创建Huffm树

public static HuffmanNode createHuffmanTree(List<HuffmanNode> nodes){
        if (nodes.size() == 0 || nodes == null){
            System.out.println("生成的List为空,不能生成霍夫曼树");
            return null;
        }
        while (nodes.size() > 1){
            Collections.sort(nodes);

            HuffmanNode leftNode = nodes.get(0);
            HuffmanNode rightNode = nodes.get(1);
            HuffmanNode parent = new HuffmanNode(null,leftNode.weight+rightNode.weight);

            parent.left = leftNode;
            parent.right = rightNode;

            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parent);

        }
        return nodes.get(0);
    }

  4.将传入的Huffman树进行Huffman编码

/*将传入所有节点的Node节点的Huffman编码得到*/
/*node 传入的节点*/
/*code 路径,向左为0,向右为1*/
/*StringBuild 用于拼接路径,生成编码*/
public static void getCode(HuffmanNode node,String code,StringBuilder stringBuilder){
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        /*将code加入到stringBuilder2 中*/
        stringBuilder2.append(code);
        if (node!=null){ // 如果node是null,则不进行处理
            if (node.data == null){ // 是非叶子节点
                //向左递归
                getCode(node.left,"0",stringBuilder2);
                //向右递归
                getCode(node.right,"1",stringBuilder2);
            }else { /*此时表明是叶子结点,说明找到了一条路径的最后*/
                huffmanCode.put(node.data,stringBuilder2.toString());
            }
        }
    }

/*方便调用,重载此方法*/
public static Map<Byte,String> getCode(HuffmanNode root){
        if (root == null){
            System.out.println("没有生成霍夫曼树");
            return null;
        }else {
            /*处理root左子树*/
            getCode(root.left,"0",stringBuilder);
            /*处理root右子树*/
            getCode(root.right,"1",stringBuilder);
        }
        return huffmanCode;
    }

  5.使用Huffman编码进行压缩

/*将字符串对应的byte数组,通过生成的Huffman编码表,返回一个Huffman编码压缩后的byte数组*/
/*bytes 原始字符串对应的字节数组*/
/*huffmanCode 生成的Huffman编码表*/
/* 返回Huffman编码处理后的字节数组*/
public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCode){
        if (bytes.length == 0 || bytes == null){
            System.out.println("字符串为空,无法进行编码");
            return null;
        }
        /*1.根据HuffmanCode获取原始的字节数组的二进制的字符串*/
        StringBuilder stb = new StringBuilder();
        for (byte b : bytes) {
            stb.append(huffmanCode.get(b));
        }
        /*2.创建存储压缩后的字节数组*/
        int index = 0; //记录第几个byte
        int len = 0; // 确定霍夫曼编码的长度
        if (stb.length() % 8 == 0){
            len = stb.length() / 8;
        }else {
            len = stb.length() / 8 + 1;
        }
        byte[] huffmanCodeBytes = new byte[len];
        /*每8位对应一个byte,所以步长+8*/
        for (int i = 0; i < stb.length();i+=8){
            String strByte = null;
            if (i+8 > stb.length()){ // 不够8位,直接从当前截取到末尾
                strByte = stb.substring(i);
            }else {
                strByte = stb.substring(i,i+8); //否则按照每8位进行拼接
            }
            /*将strByte 转换成一个个byte,放在要返回的字节数组中,进行返回*/
            huffmanCodeBytes[index++] = (byte)Integer.parseInt(strByte,2);
        }
        return huffmanCodeBytes;
    }

查看编码的结果:   压缩率   (49-21) / 49 = 57.14%

压缩之后的字节数组是:

将上述Huffman编码的步骤封装

public static byte[] getZip(byte[] bytes){
        List<HuffmanNode> nodes = getNodes(bytes);
        // 根据nodes创建赫夫曼树
        HuffmanNode root = createHuffmanTree(nodes);
        // 根据root节点生成霍夫曼编码
        huffmanCode = getCode(root);
        // 根据霍夫曼编码,对数据进行压缩,得到字节数组
        byte[] huffmanCodeBytes = zip(bytes,huffmanCode);
       // System.out.println(Arrays.toString(huffmanCodeBytes));
        return huffmanCodeBytes;
    }

三.使用Huffman进行解码

  1.将一个二进制的byte,装换为二进制的字符串

/**
     * 将一个byte转换成二进制的字符串
     * @param flag 表示是否要进行补高位,如果是true则需要补高位,false则不需要补位,如果是最后一个字节不需要补高位
     * @param b
     * @return  是该byte对应的二进制字符串(补码返回)
     */
    public static String byteToBitString(boolean flag,byte b){
        int temp = b;  /* 使用临时变量,将byte转换为int*/
        if (flag){ /*如果是一个正数,需要进行补位操作*/
            temp |= 256; /*按位与操作*/
        }
        String str = Integer.toBinaryString(temp); /*返回temp对应的二进制补码*/
        if (flag){ // 如果有8位,则按照8位来返回,否则直接返回字符串
            return str.substring(str.length()-8);
        }else {
            return str;
        }
    }

  2.解码操作

 /**
     *
     * @param huffmanCode   对应霍夫曼编码表
     * @param huffmanBytes  霍夫曼编码得到的字节数组
     * @return 原先字符串对应的字节数组
     */
    public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){
        /*1.先得到HuffmanBytes对应的二进制的字符串*/
        StringBuilder sbt = new StringBuilder();
        //将byte字节转换为二进制字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
            byte b = huffmanBytes[i];
            // 判断是否是最后一个字节
            boolean flag = (i == huffmanBytes.length-1);
            sbt.append(byteToBitString(!flag,b));
        }

        /*2.把字符串按照指定的方式进行霍夫曼解码*/
        /*把Huffman码表进行调换,因为是反向查询*/
        Map<String,Byte> map = new HashMap<>();
        for (Map.Entry<Byte,String> entry:huffmanCode.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }

        /*3.创建集合,存放解码后的byte*/
        List<Byte> byteList = new ArrayList<>();
        /*使用索引不停的扫描stb*/
        for (int k = 0; k < sbt.length();){
            int count = 1;  /*小的计数器,用于判断是否字符串是否在Huffman的码标中*/
            Byte b = null;  /*用于存放编码后的字节*/
            boolean loop = true;
            while (loop){
                /*k不动,让count进行移动,指定匹配到一个字符*/
                String key = sbt.substring(k,k+count);
                b = map.get(key);
                if (b == null){ //没有匹配到
                    count++;
                }else {
                    //匹配到就退出循环
                    loop = false;
                }
            }
            byteList.add(b);
            k += count;  //k直接移动到count在进行下一次遍历
        }

        /*4.当for循环结束后,将list中存放的数据放入到byte数组中返回即可*/
        byte[] decodeByteCodes = new byte[byteList.size()];
        for (int j = 0; j < decodeByteCodes.length; j++) {
            decodeByteCodes[j] = byteList.get(j);
        }
        return decodeByteCodes;
    }

查看解码后的结果:

完整代码

package data.structer.tree;

import java.util.*;

public class HuffmanCodeDemo {

    static Map<Byte,String> huffmanCode = new HashMap<>();
    static StringBuilder stringBuilder = new StringBuilder();

    public static void main(String[] args) {
        String content = "i like like like java do you like a java oh oh oh";
        //System.out.println("原始的长度是:"+content.length());

        byte[] bytes = getZip(content.getBytes());
       // System.out.println("Huffman编码后的字符串长度是:"+bytes.length);
        System.out.println("解码后的字符串是:"+new String(decode(huffmanCode,bytes)));
    }

    // 解码

    /**
     *
     * @param huffmanCode   对应霍夫曼编码表
     * @param huffmanBytes  霍夫曼编码得到的字节数组
     * @return
     */
    public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){
        StringBuilder sbt = new StringBuilder();
        //将byte字节转换为二进制字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
            byte b = huffmanBytes[i];
            // 判断是否是最后一个字节
            boolean flag = (i == huffmanBytes.length-1);
            sbt.append(byteToBitString(!flag,b));
        }

        //把字符串按照指定的方式进行霍夫曼解码
        Map<String,Byte> map = new HashMap<>();
        for (Map.Entry<Byte,String> entry:huffmanCode.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }

        // 创建集合,存放byte
        List<Byte> byteList = new ArrayList<>();
        for (int k = 0; k < sbt.length();){
            int count = 1;
            Byte b = null;
            boolean loop = true;
            while (loop){
                String key = sbt.substring(k,k+count);
                b = map.get(key);
                if (b == null){ //没有匹配到
                    count++;
                }else {
                    loop = false;
                }
            }
            byteList.add(b);
            k += count;
        }

        byte[] decodeByteCodes = new byte[byteList.size()];
        for (int j = 0; j < decodeByteCodes.length; j++) {
            decodeByteCodes[j] = byteList.get(j);
        }
        return decodeByteCodes;
    }

    /**
     * 将一个byte转换成二进制的字符串
     * @param flag 表示是否要进行补高位,如果是true则需要补高位,false则不需要补位,如果是最后一个字节不需要补高位
     * @param b
     * @return  是该byte对应的二进制字符串(补码返回)
     */
    public static String byteToBitString(boolean flag,byte b){
        int temp = b;
        if (flag){
            temp |= 256;
        }
        String str = Integer.toBinaryString(temp);
        if (flag){
            return str.substring(str.length()-8);
        }else {
            return str;
        }
    }

    public static byte[] getZip(byte[] bytes){
        List<HuffmanNode> nodes = getNodes(bytes);
        // 根据nodes创建赫夫曼树
        HuffmanNode root = createHuffmanTree(nodes);
        // 根据root节点生成霍夫曼编码
        huffmanCode = getCode(root);
        // 根据霍夫曼编码,对数据进行压缩,得到字节数组
        byte[] huffmanCodeBytes = zip(bytes,huffmanCode);
       // System.out.println(Arrays.toString(huffmanCodeBytes));
        return huffmanCodeBytes;
    }

    /**
     * 统计字符串中每个字符出现的次数,添加到List中进行返回
     * @param bytes
     * @return
     */
    public static List<HuffmanNode> getNodes(byte[] bytes){
        if (bytes.length == 0 || bytes == null){
            System.out.println("字符串为空,无法进行编码");
            return null;
        }
        List<HuffmanNode> nodes = new ArrayList<>();
        Map<Byte,Integer> counts = new HashMap<>();

        for (byte item : bytes) {
           Integer count = counts.get(item);
           if (count == null){ // 说明是第一次
               counts.put(item,1);
           }else {
               counts.put(item,count+1);
           }
        }

        for (Map.Entry<Byte,Integer> node:counts.entrySet()){
            nodes.add(new HuffmanNode(node.getKey(),node.getValue()));
        }
        return nodes;
    }

    /**
     * 使用霍夫曼编码进行压缩
     * @param bytes
     * @param huffmanCode
     * @return
     */
    public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCode){
        if (bytes.length == 0 || bytes == null){
            System.out.println("字符串为空,无法进行编码");
            return null;
        }
        StringBuilder stb = new StringBuilder();
        for (byte b : bytes) {
            stb.append(huffmanCode.get(b));
        }
        int index = 0;
        int len = 0; // 确定霍夫曼编码的长度
        if (stb.length() % 8 == 0){
            len = stb.length() / 8;
        }else {
            len = stb.length() / 8 + 1;
        }
        byte[] huffmanCodeBytes = new byte[len];

        for (int i = 0; i < stb.length();i+=8){
            String strByte = null;
            if (i+8 > stb.length()){ // 不够8位
                strByte = stb.substring(i);
            }else {
                strByte = stb.substring(i,i+8);
            }
            huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);
            index++;
        }
        return huffmanCodeBytes;
    }

    public static void getCode(HuffmanNode node,String code,StringBuilder stringBuilder){
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        stringBuilder2.append(code);
        if (node!=null){
            if (node.data == null){ // 是非叶子节点
                //向左递归
                getCode(node.left,"0",stringBuilder2);
                //向右递归
                getCode(node.right,"1",stringBuilder2);
            }else {
                huffmanCode.put(node.data,stringBuilder2.toString());
            }
        }
    }

    public static Map<Byte,String> getCode(HuffmanNode root){
        if (root == null){
            System.out.println("没有生成霍夫曼树");
            return null;
        }else {
            getCode(root.left,"0",stringBuilder);
            getCode(root.right,"1",stringBuilder);
        }
        return huffmanCode;
    }

    /**
     * 生成霍夫曼树
     * @param nodes
     * @return
     */
    public static HuffmanNode createHuffmanTree(List<HuffmanNode> nodes){
        if (nodes.size() == 0 || nodes == null){
            System.out.println("生成的List为空,不能生成霍夫曼树");
            return null;
        }
        while (nodes.size() > 1){
            Collections.sort(nodes);

            HuffmanNode leftNode = nodes.get(0);
            HuffmanNode rightNode = nodes.get(1);
            HuffmanNode parent = new HuffmanNode(null,leftNode.weight+rightNode.weight);

            parent.left = leftNode;
            parent.right = rightNode;

            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parent);

        }
        return nodes.get(0);
    }
}

class HuffmanNode implements Comparable<HuffmanNode>{
    public Byte data;
    public int weight;
    public HuffmanNode left;
    public HuffmanNode right;

    public HuffmanNode(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    public HuffmanNode() {
    }

    @Override
    public String toString() {
        return "HuffmanNode{" +
                "data=" + data +
                ", weight=" + weight +
                ‘}‘;
    }

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

}

原文地址:https://www.cnblogs.com/luhuajun/p/12303825.html

时间: 2024-10-12 09:57:05

Huffman编码和解码的相关文章

Huffman编码与解码的实现

Huffman编码相信学过数据结构这么课的都知道,概念也比较好理解,但是一般好理解的算法,在实际实现的过程中总是会遇到各种问题,一方面个人认为是对算法的实现过程不熟,另一方面在实际实现的过程中可以提升自己实现算法的能力,将自己的想法实现后还是比较满足的.下面是本人亲自实现的Huffman编码与解码的C语言实现,主要是记录一下自己当时的想法,供以后备忘吧. 数据结构定义如下: typedef struct{ unsigned int weight; unsigned int parent,lchi

Huffman 编码压缩算法

前两天发布那个rsync算法后,想看看数据压缩的算法,知道一个经典的压缩算法Huffman算法.相信大家应该听说过 David Huffman 和他的压缩算法—— Huffman Code,一种通过字符出现频率,Priority Queue,和二叉树来进行的一种压缩算法,这种二叉树又叫Huffman二叉树 —— 一种带权重的树.从学校毕业很长时间的我忘了这个算法,但是网上查了一下,中文社区内好像没有把这个算法说得很清楚的文章,尤其是树的构造,而正好看到一篇国外的文章<A Simple Examp

Huffman对文件编码和解码

考核实验中的一个,我也认为较为难的一个,其实也不是很难,只是有点复杂,只要分解成多个问题去解决就好了 比如你要知道Huffman是怎样对文件进行编码和解码的 然后需要知道怎么去建Huffman二叉树,建好了Huffman树 然后就是对其进行编码  最后是解码 只要把每个过程弄清楚了,用几天时间(对本科生而言)写出来应该还是有可能的 下面的代码是某位同学写的,经过其本人允许我决定贴到我的博客中供大家学习,但是如果按照源代码来演示的话,你懂得-- 期中要编码的txt文件必须放在.cpp源文件同个文件

Jcompress: 一款基于huffman编码和最小堆的压缩、解压缩小程序

前言 最近基于huffman编码和最小堆排序算法实现了一个压缩.解压缩的小程序.其源代码已经上传到github上面: Jcompress下载地址 .在本人的github上面有一个叫Utility的repository,该分类下面有一个名为Jcompress的目录便是本文所述的压缩.解压缩小程序的源代码.后续会在Utility下面增加其他一些实用的小程序,比如基于socket的文件断点下载小程序等等.如果你读了此文觉得还不错,不防给笔者的github点个star, 哈哈.在正式介绍Jcompres

Huffman编码之文件的解/压缩

问题描述:           生活中文件压缩技术可谓随处可见,在数据的密集型传输中文件压缩是一项重要的实用性技术.例如:较大文件的下载,传输等.常见的文件压缩工具有winRAR,2345好压,快压(KuaiZip)等,这些工具已经开发的相当牛逼,但是作为入门级的程序员来说,不能只停留在观摩的立场上,扮演使用者的角色.有必要深入了解其底层的基础实现方式,掌握基础的文件压缩原理,所以在此将其视为一个小型项目列出,以供大家交流探讨,相互学习.... ★在此之前,先来说说什么是文件压缩,用以抛出一个基

Huffman编码实现压缩解压缩

这是我们的课程中布置的作业,找一些资料将作业完成,顺便将其写到博客,以后看起来也方便. 原理介绍 什么是Huffman压缩 Huffman( 哈夫曼 ) 算法在上世纪五十年代初提出来了,它是一种无损压缩方法,在压缩过程中不会丢失信息熵,而且可以证明 Huffman 算法在无损压缩算法中是最优的. Huffman 原理简单,实现起来也不困难,在现在的主流压缩软件得到了广泛的应用.对应用程序.重要资料等绝对不允许信息丢失的压缩场合, Huffman 算法是非常好的选择. 怎么实现Huffman压缩

【POJ1521】【HDU1053】Entropy 哈夫曼(Huffman)编码

#include <stdio.h> int main() { puts("转载请注明出处谢谢"); puts("http://blog.csdn.net/vmurder/article/details/43020921"); } 题意: 输出字符串的长度*8.huffman编码长度.两者比值. 题解: huffman编码: 我们发现对于一个字符串,如果我们把它变成01串,比如ABCDE 那么我们需要 A : 000 B : 001 C : 010 D

[老文章搬家] 关于 Huffman 编码

按:去年接手一个项目,涉及到一个一个叫做Mxpeg的非主流视频编码格式,编解码器是厂商以源代码形式提供的,但是可能代码写的不算健壮,以至于我们tcp直连设备很正常,但是经过一个UDP数据分发服务器之后,在偶尔有丢包的情况下解码器会偶发崩溃,翻了翻他们的代码觉得可能问题出在Huffman这一块.水平有限也没有看太懂他们的源码,而且我也不是科班出身当时对Huffman编码算法只是知道这么个名字,还好服务端软件那边做了修改,解决了丢包的问题.在回家过年的火车上想起这件事,阅读了一些关于Huffman编

20172303 2018-2019-1《程序设计与数据结构》哈夫曼树编码与解码

20172303 2018-2019-1<程序设计与数据结构>哈夫曼树编码与解码 哈夫曼树简介 定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree).哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近. 带权路径长度(Weighted Path Length of Tree,简记为WPL) 结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数. 结点的带权路径长度:结点到树根之间的路径长度与