Java学习之日撸代码300行(21-30天,树与二叉树)

原博文:minfanphd

第21天: 二叉树的深度遍历的递归实现

21.1
二又树节点的两个孩子节点,一个被称为左孩子(left child),一个被称为右孩子(right child)这两个孩子节点的顺序是固定的,就像人的左手就是左手,右手就是右手,不能够颠倒或混淆。

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

21.2 二叉树的遍历

  • 深度优先遍历:所谓深度优先,顾名思义,就是偏向于纵深,“一头扎到底”的访问方式。
    • 前序遍历:二叉树的前序遍历,输出顺序是根节点、左子树、右子树。
    • 中序遍历:二叉树的中序遍历,输出顺序是左子树、根节点、右子树。
    • 后序遍历:二叉树的中序遍历,输出顺序是左子树、右子树、根节点。

在这里插入图片描述

代码:

package DataStructure.tree;

/**
 * @description:二叉树
 * @author: Qing Zhang
 * @time: 2021/5/30
 */
public class BinaryCharTree {
    //存储节点值
    char value;

    //左孩子
    BinaryCharTree leftChild;

    //右孩子
    BinaryCharTree rightChild;

    /**
     * @Description: 第一个构造函数
     * @Param: [paraValue]
     * @return:
     */
    public BinaryCharTree(char paraValue) {
        this.value = paraValue;
        leftChild = null;
        rightChild = null;
    }

    /**
     * @Description: 创建一个二叉树
     * @Param: []
     * @return: DataStructure.tree.BinaryCharTree
     */
    public static BinaryCharTree manualConstructTree() {
        // 先创建一个节点作为根节点
        BinaryCharTree resultTree = new BinaryCharTree('a');

        // 接下来创建所有的节点
        BinaryCharTree tempTreeB = new BinaryCharTree('b');
        BinaryCharTree tempTreeC = new BinaryCharTree('c');
        BinaryCharTree tempTreeD = new BinaryCharTree('d');
        BinaryCharTree tempTreeE = new BinaryCharTree('e');
        BinaryCharTree tempTreeF = new BinaryCharTree('f');
        BinaryCharTree tempTreeG = new BinaryCharTree('g');

        // 将所有节点链接起来
        resultTree.leftChild = tempTreeB;
        resultTree.rightChild = tempTreeC;
        tempTreeB.rightChild = tempTreeD;
        tempTreeC.leftChild = tempTreeE;
        tempTreeD.leftChild = tempTreeF;
        tempTreeD.rightChild = tempTreeG;

        return resultTree;
    }

    /**
     * @Description: 先序遍历
     * 遍历二叉树,节点顺序为根->左->右
     * @Param: []
     * @return: void
     */
    public void preOrderVisit() {
        System.out.print(value + " ");

        if (leftChild != null) {
            leftChild.preOrderVisit();
        }

        if (rightChild != null) {
            rightChild.preOrderVisit();
        }
    }

    /**
     * @Description: 中序遍历
     * 遍历二叉树,节点顺序为左->根->右
     * @Param: []
     * @return: void
     */
    public void inOrderVisit() {
        if (leftChild != null) {
            leftChild.inOrderVisit();
        }

        System.out.print(value + " ");


        if (rightChild != null) {
            rightChild.inOrderVisit();
        }
    }

    /**
     * @Description: 后序遍历
     * 遍历二叉树,节点顺序为左->右->根
     * @Param: []
     * @return: void
     */
    public void postOrderVisit() {
        if (leftChild != null) {
            leftChild.postOrderVisit();
        }

        if (rightChild != null) {
            rightChild.postOrderVisit();
        }

        System.out.print(value + " ");
    }

    /**
     * @Description: 二叉树的深度
     * 运用递归的思想
     * @Param: []
     * @return: int
     */
    public int getDepth() {
        if (leftChild == null && rightChild == null) {
            return 1;
        }

        int tpLeftDepth = 0, tpRightDepth = 0;

        //求左孩子的深度
        if (leftChild != null) {
            tpLeftDepth = leftChild.getDepth();
        }

        //求右孩子的深度
        if (rightChild != null) {
            tpRightDepth = rightChild.getDepth();
        }

        //返回最深孩子的深度+1,也就是还包括这一层的层数
        return tpLeftDepth > tpRightDepth ? tpLeftDepth + 1 : tpRightDepth + 1;
    }

    /**
     * @Description: 获取二叉树的所有节点
     * @Param: []
     * @return: int
     */
    public int getNumNodes() {
        if (leftChild == null && rightChild == null) {
            return 1;
        }

        int tpLeftNodes = 0, tpRightNodes = 0;
        //求左孩子的节点数
        if (leftChild != null) {
            tpLeftNodes = leftChild.getNumNodes();
        }

        //求右孩子的节点数
        if (rightChild != null) {
            tpRightNodes = rightChild.getNumNodes();
        }

        //返回当前节点加孩子包含的节点数
        return tpLeftNodes + tpRightNodes + 1;
    }

    public static void main(String[] args) {
        BinaryCharTree tempTree = manualConstructTree();
        System.out.println("\r\n先序遍历:");
        tempTree.preOrderVisit();
        System.out.println("\r\n中序遍历:");
        tempTree.inOrderVisit();
        System.out.println("\r\n后序遍历:");
        tempTree.postOrderVisit();

        System.out.println("\r\n\r\n该树深度为: " + tempTree.getDepth());
        System.out.println("该树的总结点数为: " + tempTree.getNumNodes());
    }
}

第22天: 二叉树的存储

22.1 首先该存储是用的上一天的二叉树数据,如下图所示:
在这里插入图片描述

此时空使用 0 来表示, 可以用一个向量来存储:
[a, b, c, 0, d, e, 0, 0, 0, f, g]
若将节点的值和位置分开存储,则可以用两个向量存储:
[a, b, c, d, e, f, g]
[0, 1, 2, 4, 5, 9, 10]

22.2 在采用Object队列时,将其转换为int类型时发现了一个错误,那就是在tpTree为null时,此时tpIntTree也为null,但是null无法转换为int类型,因此此时需要添加判断,看tpTree是否为null。

在这里插入图片描述

添加的Object队列,就不需要针对各种类型的数据去反复创建队列了,代码如下:

package DataStructure.queue;

/**
 * @description:循环队列(以Object为对象)
 * @author: Qing Zhang
 * @time: 2021/5/30
 */
public class CircleObjectQueue {

    //队列长度,有一个空间不能使用
    public static final int TOTAL_SPACE = 10;

    //存储数据
    Object[] data;


    //队头
    int head;

    //队尾
    int tail;

    /**
     * @Description: 构造函数
     * @Param: []
     * @return:
     */
    public CircleObjectQueue() {
        data = new Object[TOTAL_SPACE];
        head = 0;
        tail = 0;
    }

    /**
     * @Description: 入队
     * @Param: [paraValue]
     * @return: void
     */
    public void enqueue(Object paraValue) {
        if ((tail + 1) % TOTAL_SPACE == head) {
            System.out.println("队列已满。");
            return;
        }

        data[tail % TOTAL_SPACE] = paraValue;
        tail++;
    }

    /**
     * @Description: 出队
     * @Param: []
     * @return: java.lang.Object
     */
    public Object dequeue() {
        if (head == tail) {
            System.out.println("队列中无元素。");
            return null;
        }

        Object resultValue = data[head];

        head++;

        return resultValue;
    }


    public String toString() {
        String resultString = "";

        if (head == tail) {
            return "empty";
        }

        for (int i = head; i < tail; i++) {
            resultString += data[i % TOTAL_SPACE] + ", ";
        }

        return resultString;
    }


    public static void main(String args[]) {
        CircleObjectQueue tpQueue = new CircleObjectQueue();
    }
}

在二叉树中添加的代码:

 //层次遍历后的节点值
    char[] valuesArray;

    //完全二叉树中对应的下标
    int[] indicesArray;

    /** 
    * @Description: 存储二叉树的位置
    * @Param: []
    * @return: void
    */
    public void toDataArrays() {
        //获取当前树的节点数
        int tpLength = getNumNodes();

        valuesArray = new char[tpLength];
        indicesArray = new int[tpLength];
        int i = 0;

        CircleObjectQueue tpQueue = new CircleObjectQueue();
        tpQueue.enqueue(this);
        CircleObjectQueue tpIntQueue = new CircleObjectQueue();
        tpIntQueue.enqueue(0);

        BinaryCharTree tpTree = (BinaryCharTree) tpQueue.dequeue();
        int tpIndex = (int) tpIntQueue.dequeue();

        while (tpTree != null) {
            valuesArray[i] = tpTree.value;
            indicesArray[i] = tpIndex;
            i++;

            if (tpTree.leftChild != null) {
                tpQueue.enqueue(tpTree.leftChild);
                tpIntQueue.enqueue(tpIndex * 2 + 1);
            }

            if (tpTree.rightChild != null) {
                tpQueue.enqueue(tpTree.rightChild);
                tpIntQueue.enqueue(tpIndex * 2 + 2);
            }

            tpTree = (BinaryCharTree) tpQueue.dequeue();
            if(tpTree!=null){
                tpIndex = (int) tpIntQueue.dequeue();
            }
        }

    }


    public static void main(String[] args) {
        BinaryCharTree tempTree = manualConstructTree();
        System.out.println("\r\n先序遍历:");
        tempTree.preOrderVisit();
        System.out.println("\r\n中序遍历:");
        tempTree.inOrderVisit();
        System.out.println("\r\n后序遍历:");
        tempTree.postOrderVisit();

        System.out.println("\r\n\r\n该树深度为: " + tempTree.getDepth());
        System.out.println("该树的总结点数为: " + tempTree.getNumNodes());

        tempTree.toDataArrays();
        System.out.println("The values are: " + Arrays.toString(tempTree.valuesArray));
        System.out.println("The indices are: " + Arrays.toString(tempTree.indicesArray));
    }

第23天: 使用具有通用性的队列

在昨天自己做的时候就顺便把这部分给做了,当时就在想为什么不直接将全部类型的队列直接强制转换就可以了,直接贴上昨天的部分代码即可。

代码:

CircleObjectQueue tpQueue = new CircleObjectQueue();
        tpQueue.enqueue(this);
        CircleObjectQueue tpIntQueue = new CircleObjectQueue();
        tpIntQueue.enqueue(0);

        BinaryCharTree tpTree = (BinaryCharTree) tpQueue.dequeue();
        int tpIndex = (int) tpIntQueue.dequeue();

第24天:二叉树的建立

24.1 今天其实就是利用昨天提出的概念,通过节点以及节点位置来生成二叉树。
24.2 主要的重点就是需要去根据节点位置往前判断,看当前节点是否是之前节点的左孩子节点还是右孩子节点。

在这里插入图片描述

代码:

/** 
    * @Description: 构造函数
     * 根据节点以及节点下标来生成二叉树
    * @Param: [paraDataArray, paraIndicesArray]
    * @return: 
    */
    public BinaryCharTree(char[] paraDataArray, int[] paraIndicesArray) {
        //首先使用一个顺序表存储所有的节点
        int tpNumNodes = paraDataArray.length;
        BinaryCharTree[] tpAllNodes = new BinaryCharTree[tpNumNodes];
        for (int i = 0; i < tpNumNodes; i++) {
            tpAllNodes[i] = new BinaryCharTree(paraDataArray[i]);
        }

        //其次再将这些节点链接起来
        for (int i = 1; i < tpNumNodes; i++) {
            for (int j = 0; j < i; j++) {
                System.out.println("indices " + paraIndicesArray[j] + " vs. " + paraIndicesArray[i]);
                if (paraIndicesArray[i] == paraIndicesArray[j] * 2 + 1) {
                    tpAllNodes[j].leftChild = tpAllNodes[i];
                    System.out.println("Linking " + j + " with " + i);
                    break;
                } else if (paraIndicesArray[i] == paraIndicesArray[j] * 2 + 2) {
                    tpAllNodes[j].rightChild = tpAllNodes[i];
                    System.out.println("Linking " + j + " with " + i);
                    break;
                }
            }
        }

        value = tpAllNodes[0].value;
        leftChild = tpAllNodes[0].leftChild;
        rightChild = tpAllNodes[0].rightChild;
    }
    
    public static void main(String[] args) {
        BinaryCharTree tempTree = manualConstructTree();
        System.out.println("\r\n先序遍历:");
        tempTree.preOrderVisit();
        System.out.println("\r\n中序遍历:");
        tempTree.inOrderVisit();
        System.out.println("\r\n后序遍历:");
        tempTree.postOrderVisit();

        System.out.println("\r\n\r\n该树深度为: " + tempTree.getDepth());
        System.out.println("该树的总结点数为: " + tempTree.getNumNodes());

        tempTree.toDataArrays();
        System.out.println("The values are: " + Arrays.toString(tempTree.valuesArray));
        System.out.println("The indices are: " + Arrays.toString(tempTree.indicesArray));

        char[] tempCharArray = {'A', 'B', 'C', 'D', 'E', 'F'};
        int[] tempIndicesArray = {0, 1, 2, 4, 5, 12};
        BinaryCharTree tempTree2 = new BinaryCharTree(tempCharArray, tempIndicesArray);

        System.out.println("\r\n先序遍历:");
        tempTree2.preOrderVisit();
        System.out.println("\r\n中序遍历:");
        tempTree2.inOrderVisit();
        System.out.println("\r\n后序遍历:");
        tempTree2.postOrderVisit();
    }

第25天:二叉树深度遍历的栈实现 (中序)

25.1 具有通用性的对象栈

将栈也改为了更具通用性,如同之前的队列一样,其中大部分内容没有更改,不过这里将判断栈是否为空专门独立成了一个函数。

package DataStructure.stack;

/**
 * @description:
 * @author: Qing Zhang
 * @time: 2021/6/6
 */
public class ObjectStack {
    //栈的最大长度
    public static final int MAX_DEPTH = 10;

    //栈的当前长度
    int depth;

    //存储数据
    Object[] data;

    /**
     * @Description: 构造函数
     * @Param: []
     * @return:
     */
    public ObjectStack() {
        depth = 0;
        data = new Object[MAX_DEPTH];
    }

    public String toString() {
        String resultString = "";
        for (int i = 0; i < depth; i++) {
            resultString += data[i];
        }
        return resultString;
    }

    /**
     * @Description: 入栈
     * @Param: [paraValue]
     * @return: boolean
     */
    public boolean push(Object  paraObject) {
        if (depth == MAX_DEPTH) {
            System.out.println("Stack full.");
            return false;
        }

        data[depth++] = paraObject;

        return true;
    }

    /**
     * @Description:出栈 将栈顶元素返回,并将栈的长度减一
     * @Param: []
     * @return: java.lang.Object
     */
    public Object pop() {
        if (depth == 0) {
            System.out.println("Nothing to pop.");
            return '\0';
        }
        Object resultObject = data[--depth];
        return resultObject;
    }

    /**
     * @Description: 判断栈是否为空
     * @Param: []
     * @return: boolean
     */
    public boolean isEmpty() {
        if (depth == 0) {
            return true;
        }

        return false;
    }

    public static void main(String[] args) {
        ObjectStack tempStack = new ObjectStack();

        for (char ch = 'a'; ch < 'm'; ch++) {
            tempStack.push(ch);
            System.out.println("当前栈为: " + tempStack);
        }

        char tempChar;
        for (int i = 0; i < 12; i++) {
            tempChar = (char) tempStack.pop();
            System.out.println("出栈: " + tempChar);
            System.out.println("当前栈为: " + tempStack);
        }
    }
}

25.2 中序遍历

利用栈的原理其实就是根据节点遍历顺序左右根去依次入栈到最深处,栈本来就可以实现递归的作用,因此这里先一直从左孩子入栈到最左下方的节点,然后再往上出栈,同时再往右孩子进行之前的步骤。

在这里插入图片描述

/** 
    * @Description: 中序遍历
     * 采用栈
    * @Param: []
    * @return: void
    */
    public void inOrderVisitWithStack() {
        ObjectStack tpStack = new ObjectStack();
        BinaryCharTree tpNode = this;
        while (!tpStack.isEmpty() || tpNode != null) {
            if (tpNode != null) {
                tpStack.push(tpNode);
                tpNode = tpNode.leftChild;
            } else {
                tpNode = (BinaryCharTree) tpStack.pop();
                System.out.print("" + tpNode.value + " ");
                tpNode = tpNode.rightChild;
            }
        }
    }

    public static void main(String[] args) {
        BinaryCharTree tempTree = manualConstructTree();
        System.out.println("\r\n先序遍历:");
        tempTree.preOrderVisit();
        System.out.println("\r\n中序遍历:");
        tempTree.inOrderVisit();
        System.out.println("\r\n后序遍历:");
        tempTree.postOrderVisit();

        System.out.println("\r\n\r\n该树深度为: " + tempTree.getDepth());
        System.out.println("该树的总结点数为: " + tempTree.getNumNodes());

        tempTree.toDataArrays();
        System.out.println("The values are: " + Arrays.toString(tempTree.valuesArray));
        System.out.println("The indices are: " + Arrays.toString(tempTree.indicesArray));

        char[] tempCharArray = {'A', 'B', 'C', 'D', 'E', 'F'};
        int[] tempIndicesArray = {0, 1, 2, 4, 5, 12};
        BinaryCharTree tempTree2 = new BinaryCharTree(tempCharArray, tempIndicesArray);

        System.out.println("\r\n先序遍历:");
        tempTree2.preOrderVisit();
        System.out.println("\r\n中序遍历:");
        tempTree2.inOrderVisit();
        System.out.println("\r\n后序遍历:");
        tempTree2.postOrderVisit();

        System.out.println("\r\n用栈实现的中序遍历:");
        tempTree2.inOrderVisitWithStack();
    }

第26天:二叉树深度遍历的栈实现 (前序和后序)

前序与中序并无多大差别
后序遍历涉及到的思想是根据前序遍历的顺序(根左右),前者为(左右根),因此可以先将前序遍历中的左右孩子顺序调换一下,最后再将输出结果逆向输出即得到了后序遍历结果。

在这里插入图片描述

    /**
    * @Description: 前序遍历
     * 用栈
    * @Param: []
    * @return: void
    */
    public void preOrderVisitWithStack() {
        ObjectStack tpStack = new ObjectStack();
        BinaryCharTree tpNode = this;
        while (!tpStack.isEmpty() || tpNode != null) {
            if (tpNode != null) {
                System.out.print("" + tpNode.value + " ");
                tpStack.push(tpNode);
                tpNode = tpNode.leftChild;
            } else {
                tpNode = (BinaryCharTree) tpStack.pop();
                tpNode = tpNode.rightChild;
            }
        }
    }
	/**
    * @Description: 后序遍历
     * 用栈
    * @Param: []
    * @return: void
    */
    public void postOrderVisitWithStack() {
        ObjectStack tpStack = new ObjectStack();
        BinaryCharTree tpNode = this;
        ObjectStack tempOutputStack = new ObjectStack();

        while (!tpStack.isEmpty() || tpNode != null) {
            if (tpNode != null) {
                //存储输出结果
                tempOutputStack.push(tpNode.value);
                tpStack.push(tpNode);
                tpNode = tpNode.rightChild;
            } else {
                tpNode = (BinaryCharTree) tpStack.pop();
                tpNode = tpNode.leftChild;
            }
        }

        //反向输出结果.
        while (!tempOutputStack.isEmpty()) {
            System.out.print("" + tempOutputStack.pop() + " ");
        }
    }


    public static void main(String[] args) {
        BinaryCharTree tempTree = manualConstructTree();
        System.out.println("\r\n先序遍历:");
        tempTree.preOrderVisit();
        System.out.println("\r\n中序遍历:");
        tempTree.inOrderVisit();
        System.out.println("\r\n后序遍历:");
        tempTree.postOrderVisit();

        System.out.println("\r\n\r\n该树深度为: " + tempTree.getDepth());
        System.out.println("该树的总结点数为: " + tempTree.getNumNodes());

        tempTree.toDataArrays();
        System.out.println("The values are: " + Arrays.toString(tempTree.valuesArray));
        System.out.println("The indices are: " + Arrays.toString(tempTree.indicesArray));

        char[] tempCharArray = {'A', 'B', 'C', 'D', 'E', 'F'};
        int[] tempIndicesArray = {0, 1, 2, 4, 5, 12};
        BinaryCharTree tempTree2 = new BinaryCharTree(tempCharArray, tempIndicesArray);

        System.out.println("\r\n先序遍历:");
        tempTree2.preOrderVisit();
        System.out.println("\r\n中序遍历:");
        tempTree2.inOrderVisit();
        System.out.println("\r\n后序遍历:");
        tempTree2.postOrderVisit();

        System.out.println("\r\n用栈实现的中序遍历:");
        tempTree2.inOrderVisitWithStack();
        System.out.println("\r\n用栈实现的先序遍历:");
        tempTree2.preOrderVisitWithStack();
        System.out.println("\r\n用栈实现的后序遍历:");
        tempTree2.postOrderVisitWithStack();
    }

第27天:Hanoi 塔问题

在这里插入图片描述

在这里插入图片描述

将数量设为5,步骤将会极大提升。
在这里插入图片描述

package DataStructure.tree;

/**
 * @description:汉诺塔
 * @author: Qing Zhang
 * @time: 2021/6/8
 */
public class Hanoi {

    /**
    * @Description: 汉诺塔的移动函数
    * @Param: [paraSource:原始存放塔
     * , paraIntermediary:过渡塔
     * , paraDestination:目标塔
     * , paraNumber:塔上的物品数量]
    * @return: void
    */
    public static void hanoi(char paraSource, char paraIntermediary, char paraDestination,
                             int paraNumber) {
        if (paraNumber == 1) {
            System.out.println(paraSource + "->" + paraDestination + " ");
            return;
        }

        
        hanoi(paraSource, paraDestination, paraIntermediary, paraNumber - 1);
        System.out.println(paraSource + "->" + paraDestination + " ");
        hanoi(paraIntermediary, paraSource, paraDestination, paraNumber - 1);
    }

    public static void main(String args[]) {
        hanoi('a', 'b', 'c', 3);
    }
}

第28天:Huffman 编码 (节点定义与文件读取)

要想弄清楚Huffman编码就需要弄清楚其定义:

  • 哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的带权路径长度记为WPL=(W1 ∗ * L1+W2 ∗ * L2+W3 ∗ * L3+…+Wn ∗ * Ln),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。可以证明哈夫曼树的WPL是最小的。
  • 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式。根据哈夫曼树来生成相应的编码。

今天的任务主要是将哈夫曼中的一些元素定义出来,同时将目标文件的读取也实现出来。

package DataStructure.tree;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * @description:哈夫曼树
 * @author: Qing Zhang
 * @time: 2021/6/9
 */
public class Huffman {

    class HuffmanNode {

        //字符,仅对叶节点有效
        char character;

        //权值
        int weight;

        //左孩子
        HuffmanNode leftChild;

        //右孩子
        HuffmanNode rightChild;

        //父节点,其作用主要是用来组合哈夫曼节点
        HuffmanNode parent;

        public HuffmanNode(char character, int weight, HuffmanNode leftChild,
                           HuffmanNode rightChild, HuffmanNode parent) {
            this.character = character;
            this.weight = weight;
            this.leftChild = leftChild;
            this.rightChild = rightChild;
            this.parent = parent;
        }

        @Override
        public String toString() {
            String resString = "(" + character + ", " + weight + ")";
            return resString;
        }
    }

    //面向的数据限制为ASCII
    public static final int NUM_CHARS = 256;

    //存储字符串
    String inputText;

    //字符串的长度,同时也是叶节点的数量
    int alphabetLength;

    //存储出现过的字符
    char[] alphabet;

    //所有节点数量,2*n-1,n为叶节点数量
    int[] charCounts;

    //字符到字母表中索引的映射
    int[] charMapping;

    //将字符映射为哈夫曼编码
    String[] huffmanCodes;

    //存储所有的节点,最后一个为根节点
    HuffmanNode[] nodes;

    public Huffman(String paraFilename) {
        charMapping = new int[NUM_CHARS];

        readText(paraFilename);
    }

    /**
     * @Description: 读取文件
     * @Param: [paraFilename]
     * @return: void
     */
    private void readText(String paraFilename) {
        try {
            inputText = Files.newBufferedReader(Paths.get(paraFilename), StandardCharsets.UTF_8).lines().collect(Collectors.joining("\n"));
        } catch (Exception e) {
            System.out.println(e);
            System.exit(0);
        }
        System.out.println("The text is:\r\n" + inputText);
    }

第29天:Huffman 编码 (建树)

今天的任务主要是将哈夫曼树给建立起来,在这之中首先需要将字符的数据给构建起来,也就是相应的权值,然后根据出现的字符数量创建相对应的节点,最后根据哈夫曼树的定义将节点链接起来形成哈夫曼树。
难点主要是需要去理解为什么要以这种方式去构造哈夫曼树,哈夫曼树的构造方法是从当前节点中选择两个最小权值的节点构成一个新的节点,是自底向上的构造方式。

/**
    * @Description:创建字母表的映射
    * @Param: []
    * @return: void
    */
    public void constructAlphabet() {
        //初始化
        Arrays.fill(charMapping, -1);

        //用以存储每个字符的数量
        int[] tpCharCounts = new int[NUM_CHARS];

        //存储字符的ASCII值
        int tpCharIndex;

        //首先是扫描字符串获得每个字符的数量
        char tpChar;
        for (int i = 0; i < inputText.length(); i++) {
            tpChar = inputText.charAt(i);
            tpCharIndex = (int) tpChar;

            System.out.print(tpCharIndex + " ");

            tpCharCounts[tpCharIndex]++;
        }

        //决定字母表的长度
        alphabetLength = 0;
        for (int i = 0; i < 255; i++) {
            if (tpCharCounts[i] > 0) {
                alphabetLength++;
            }
        }

        //压缩
        alphabet = new char[alphabetLength];
        charCounts = new int[alphabetLength * 2 - 1];

        int tpCounter = 0;
        for (int i = 0; i < NUM_CHARS; i++) {
            if (tpCharCounts[i] > 0) {
                alphabet[tpCounter] = (char) i;
                charCounts[tpCounter] = tpCharCounts[i];
                charMapping[i] = tpCounter;
                tpCounter++;
            }
        }
        System.out.println("The alphabet is: " + Arrays.toString(alphabet));
        System.out.println("Their counts are: " + Arrays.toString(charCounts));
        System.out.println("The char mappings are: " + Arrays.toString(charMapping));
    }

    /** 
    * @Description: 构造哈夫曼树
    * @Param: []
    * @return: void
    */
    public void constructTree() {
        //分配空间
        nodes = new HuffmanNode[alphabetLength * 2 - 1];
        boolean[] tpProcessed = new boolean[alphabetLength * 2 - 1];

        //初始化节点
        for (int i = 0; i < alphabetLength; i++) {
            nodes[i] = new HuffmanNode(alphabet[i], charCounts[i], null, null, null);
        }

        //创建树
        int tpLeft, tpRight, tpMinimal;
        for (int i = alphabetLength; i < 2 * alphabetLength - 1; i++) {
            //选择一个最小权值的节点作为左孩子
            tpLeft = -1;
            tpMinimal = Integer.MAX_VALUE;
            for (int j = 0; j < i; j++) {
                if (tpProcessed[j]) {
                    continue;
                }

                if (tpMinimal > charCounts[j]) {
                    tpMinimal = charCounts[j];
                    tpLeft = j;
                }
            }
            tpProcessed[tpLeft] = true;

            //选择第二小的作为右孩子
            tpRight = -1;
            tpMinimal = Integer.MAX_VALUE;
            for (int j = 0; j < i; j++) {
                if (tpProcessed[j]) {
                    continue;
                }

                if (tpMinimal > charCounts[j]) {
                    tpMinimal = charCounts[j];
                    tpRight = j;
                }
            }
            tpProcessed[tpRight] = true;
            System.out.println("Selecting " + tpLeft + " and " + tpRight);

            //创建新的节点
            charCounts[i] = charCounts[tpLeft] + charCounts[tpRight];
            nodes[i] = new HuffmanNode('*', charCounts[i], nodes[tpLeft], nodes[tpRight], null);

            //将孩子链接上
            nodes[tpLeft].parent = nodes[i];
            nodes[tpRight].parent = nodes[i];
            System.out.println("The children of " + i + " are " + tpLeft + " and " + tpRight);

        }
    }

    //获取二叉树的根节点
    public HuffmanNode getRoot(){
        return nodes[nodes.length - 1];
    }

第30天:Huffman 编码 (编码与解码)

今天的任务则是根据前两天构建的哈夫曼树来获得哈夫曼编码,哈夫曼编码我是这样理解的,从根节点到目标叶节点的路径,路径中沿左孩子为0,沿右孩子为1,那么到达叶节点这个过程所产生的一串编码即为哈夫曼编玛。
同理解码则是根据哈夫曼编码从根节点依次往下遍历到叶节点便会自动停止,然后又从根节点开始往下遍历,直到遍历完所有编码,最终即可得到解码。

/** 
    * @Description: 哈夫曼树的先序遍历
    * @Param: [paraNode]
    * @return: void
    */
    public void preOrderVisit(HuffmanNode paraNode) {
        System.out.print("(" + paraNode.character + ", " + paraNode.weight + ") ");

        if (paraNode.leftChild != null) {
            preOrderVisit(paraNode.leftChild);
        }

        if (paraNode.rightChild != null) {
            preOrderVisit(paraNode.rightChild);
        }
    }

    /** 
    * @Description: 为每一个字符创建编码
    * @Param: []
    * @return: void
    */
    public void generateCodes() {
        huffmanCodes = new String[alphabetLength];
        HuffmanNode tpNode;
        for (int i = 0; i < alphabetLength; i++) {
            tpNode = nodes[i];

            String tpCharCode = "";
            while (tpNode.parent != null) {
                if (tpNode == tpNode.parent.leftChild) {
                    tpCharCode = "0" + tpCharCode;
                } else {
                    tpCharCode = "1" + tpCharCode;
                }

                tpNode = tpNode.parent;
            }

            huffmanCodes[i] = tpCharCode;
            System.out.println("The code of " + alphabet[i] + " is " + tpCharCode);
        }
    }

    /** 
    * @Description: 编码
    * @Param: [paraString]
    * @return: java.lang.String
    */
    public String coding(String paraString) {
        String resultCodeString = "";

        int tpIndex;
        for (int i = 0; i < paraString.length(); i++) {

            tpIndex = charMapping[(int) paraString.charAt(i)];

            resultCodeString += huffmanCodes[tpIndex];
        }
        return resultCodeString;
    }


    /**
    * @Description: 解码
    * @Param: [paraString]
    * @return: java.lang.String
    */
    public String decoding(String paraString) {
        String resultCodeString = "";

        HuffmanNode tpNode = getRoot();

        for (int i = 0; i < paraString.length(); i++) {
            if (paraString.charAt(i) == '0') {
                tpNode = tpNode.leftChild;
                System.out.println(tpNode);
            } else {
                tpNode = tpNode.rightChild;
                System.out.println(tpNode);
            }

            if (tpNode.leftChild == null) {
                System.out.println("Decode one:" + tpNode);

                resultCodeString += tpNode.character;

                tpNode = getRoot();
            }
        }

        return resultCodeString;
    }

    public static void main(String args[]) {
        Huffman tempHuffman = new Huffman("F:/研究生/研0/学习/Java_Study/data/huffmantext.txt");
        tempHuffman.constructAlphabet();

        tempHuffman.constructTree();

        HuffmanNode tempRoot = tempHuffman.getRoot();
        System.out.println("The root is: " + tempRoot);
        System.out.println("Preorder visit:");
        tempHuffman.preOrderVisit(tempHuffman.getRoot());

        tempHuffman.generateCodes();

        String tempCoded = tempHuffman.coding("abcdb");
        System.out.println("Coded: " + tempCoded);
        String tempDecoded = tempHuffman.decoding(tempCoded);
        System.out.println("Decoded: " + tempDecoded);
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值