数据结构-二叉树图解与实现

二叉树

为什么需要树?
  • 数组的查找效率高,但是插入效率低。
  • 链表的插入效率高,查找效率低。

需要一种数据结构来平衡查找与插入效率,使得查找速度和插入速度都能得到提升,因此有了树这种数据结构。

img

满二叉树

如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n为层数,则我们称为满二叉树。
在这里插入图片描述

完全二叉树

如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树
在这里插入图片描述

二叉树的遍历

前序遍历

先遍历父节点,再遍历左子节点,最后遍历右子节点

中序遍历

先遍历左子节点,再遍历父节点,最后遍历右子节点

后序遍历

先遍历左子节点,再遍历右子节点,最后遍历父节点

可以看出,前中后的区别在于父节点遍历的时机

如下案例:

在这里插入图片描述

简单实现二叉树的代码如下:

package test.tree;

/**
 *@ClassName BinaryTreeTest
 *@Description 二叉树代码测试
 *@Author tangjian
 *@Date 2022/3/22 8:05 下午
 *@Version 1.0
 **/
public class BinaryTreeTest {

    public static void main(String[] args) {

        StuTreeNode root = new StuTreeNode(1,"学生1");
        StuTreeNode stuTreeNode1 = new StuTreeNode(2,"学生2");
        StuTreeNode stuTreeNode2 = new StuTreeNode(4,"学生4");
        StuTreeNode stuTreeNode3 = new StuTreeNode(6,"学生6");
        StuTreeNode stuTreeNode4 = new StuTreeNode(8,"学生8");
        root.leftNode = stuTreeNode1;
        root.rightNode = stuTreeNode2;
        stuTreeNode2.leftNode = stuTreeNode3;
        stuTreeNode2.rightNode = stuTreeNode4;
        StuBinaryTree binaryTree = new StuBinaryTree();
        binaryTree.root = root;
        System.out.println("前序打印为:");
        binaryTree.prePrint();
        System.out.println("中序打印为:");
        binaryTree.middlePrint();
        System.out.println("后序打印为:");
        binaryTree.afterPrint();

        System.out.println("前序查找树中的学生8");
        binaryTree.preSearch(9);
        System.out.println("中序查找树中的学生8");
        binaryTree.middleSearch(8);
        System.out.println("后序查找树中的学生8");
        binaryTree.afterSearch(8);

        System.out.println("删除节点 8,然后前序打印 ");
        binaryTree.deleteNode(8);
        binaryTree.prePrint();
    }
}

class StuBinaryTree {
    StuTreeNode root;

    public void prePrint() { //前序遍历打印
        if (root == null) {
            System.out.println("当前为空");
        }
        root.prePrint();
    }


    public void middlePrint() { //中序遍历打印
        if (root == null) {
            System.out.println("当前为空");
        }
        root.middlePrint();
    }


    public void afterPrint() { //后=序遍历打印
        if (root == null) {
            System.out.println("当前为空");
        }
        root.afterPrint();
    }


    public void preSearch(int id) { //前序查找数据元素
        if (root == null) {
            System.out.println("当前为空");
        }
        root.preSearch(id);
    }


    public void middleSearch(int id) { //中序查找数据元素
        if (root == null) {
            System.out.println("当前为空");
        }
        root.middleSearch(id);
    }


    public void afterSearch(int id) { //中序查找数据元素
        if (root == null) {
            System.out.println("当前为空");
        }
        root.afterSearch(id);
    }

    //删除节点
    public void deleteNode(int id) {
        if (root == null) {
            System.out.println("当前为空");
        }
        if (root.id == id) {
            System.out.println("已删除整个二叉树");
            root = null;
        }
        root.deleteNode(id);
    }

}

class StuTreeNode {  //学生二叉树节点

    int id;
    String name;
    StuTreeNode leftNode;  //左节点
    StuTreeNode rightNode; //右节点

    public StuTreeNode(int id, String name) {
        this.id = id;
        this.name = name;
    }

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

    public void prePrint() { //前序遍历打印
        System.out.println(this); //先遍历当前自己
        if (this.leftNode != null) {
            this.leftNode.prePrint();
        }
        if (this.rightNode != null) {
            this.rightNode.prePrint();
        }
    }


    public void middlePrint() { //中序遍历打印
        if (this.leftNode != null) {
            this.leftNode.middlePrint();
        }
        System.out.println(this); //先遍历左子树再打印自己
        if (this.rightNode != null) {
            this.rightNode.middlePrint();
        }
    }


    public void afterPrint() { //后=序遍历打印
        if (this.leftNode != null) {
            this.leftNode.afterPrint();
        }
        if (this.rightNode != null) {
            this.rightNode.afterPrint();
        }
        System.out.println(this); //先遍历左子树和右子树后再打印自己
    }


    public void preSearch(int id) { //前序查找数据元素
        if (this.id == id) {
            System.out.println(this); //先遍历当前自己
            return;
        }
        if (this.leftNode != null) {
            this.leftNode.preSearch(id);
        }
        if (this.rightNode != null) {
            this.rightNode.preSearch(id);
        }
    }


    public void middleSearch(int id) { //中序查找数据元素
        if (this.leftNode != null) {
            this.leftNode.middleSearch(id);
        }
        if (this.id == id) {
            System.out.println(this); //先遍历当前自己
            return;
        }
        if (this.rightNode != null) {
            this.rightNode.middleSearch(id);
        }
    }


    public void afterSearch(int id) { //中序查找数据元素
        if (this.leftNode != null) {
            this.leftNode.afterSearch(id);
        }
        if (this.rightNode != null) {
            this.rightNode.afterSearch(id);
        }
        if (this.id == id) {
            System.out.println(this); //先遍历当前自己
            return;
        }
    }

    //前序删除节点
    public void deleteNode(int id) {
        //从要删除的父节点来进行删除,如果要删除的节点是左子节点,则将左子节点置位null,如果为右子节点,则将右子节点置位null
        if (this.leftNode != null && this.leftNode.id == id) {
            this.leftNode = null;
            System.out.println("已删除该节点 ");
            return;
        }
        if (this.rightNode != null && this.rightNode.id == id) {
            this.rightNode = null;
            System.out.println("已删除该节点");
            return;
        }
        if (this.leftNode != null) {
            this.leftNode.deleteNode(id);
        }
        if (this.rightNode != null) {
            this.rightNode.deleteNode(id);
        }
    }
}

顺序存储二叉树

基本说明

从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树树也可以转换成数组,如下图所示:
在这里插入图片描述

特点

  • 顺序二叉树通常只考虑完全二叉树
  • 第 n个元素的子节点为 2 × n + 1
  • 第 n个元素的子节点为 2 × n + 2
  • 第 n个元素的父节点为(n-1) ÷2
    • 其中n 表示二叉树中的第几个元素(从0开始编号)

遍历过程和二叉树的遍历类似,只不过递归的条件有所不同

分析 以上二叉树的前,中,后序遍历应该为:

前序:1,2,4,5,3,6,7

中序: 4,2,5,1,6,3,7

后序:4,5,2,6,7,3,1

代码实现如下:

package test.tree;

/**
 *@ClassName OrderBinaryTreeTest
 *@Description 顺序二叉树代码测试,将数组转成二叉树
 * - 第 n个元素的**左**子节点为 **2 × n + 1**
 * - 第 n个元素的**右**子节点为 **2 × n + 2**
 * - 第 n个元素的父节点为(n-1) ÷2
 *   - 其中**n** 表示二叉树中的第几个元素(从0开始编号)
 *
 *@Author tangjian
 *@Date 2022/3/24 8:25 下午
 *@Version 1.0
 **/
public class OrderBinaryTreeTest {

    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        OrderBinaryTree binaryTree = new OrderBinaryTree(arr);
        System.out.println("前序打印该顺序二叉树");
        //前序打印为
        binaryTree.prePrint(0);
        //中序打印为
        System.out.println("中序打印该顺序二叉树");
        binaryTree.middlePrint(0);
        //后序打印为
        System.out.println("后序打印该顺序二叉树");
        binaryTree.afterPrint(0);
    }

}

class OrderBinaryTree {
    int[] treeArr;

    public OrderBinaryTree(int[] treeArr) {
        this.treeArr = treeArr;
    }

    //前序遍历整个数组
    public void prePrint(int index) {
        if (treeArr == null && treeArr.length == 0) {
            System.out.println("当前顺序二叉树为null");
        }
        System.out.println(treeArr[index]);
        if ((2 * index + 1) < treeArr.length) { //2 × n + 1  遍历左子节点
            prePrint(2 * index + 1);
        }
        if ((2 * index + 2) < treeArr.length) { //2 × n + 1  遍历左子节点
            prePrint(2 * index + 2);
        }
    }


    //中序遍历整个数组
    public void middlePrint(int index) {
        if (treeArr == null && treeArr.length == 0) {
            System.out.println("当前顺序二叉树为null");
        }
        if ((2 * index + 1) < treeArr.length) { //2 × n + 1  遍历左子节点
            middlePrint(2 * index + 1);
        }
        System.out.println(treeArr[index]);
        if ((2 * index + 2) < treeArr.length) { //2 × n + 1  遍历左子节点
            middlePrint(2 * index + 2);
        }
    }

    //后序遍历整个数组
    public void afterPrint(int index) {
        if (treeArr == null && treeArr.length == 0) {
            System.out.println("当前顺序二叉树为null");
        }
        if ((2 * index + 1) < treeArr.length) { //2 × n + 1  遍历左子节点
            afterPrint(2 * index + 1);
        }
        if ((2 * index + 2) < treeArr.length) { //2 × n + 1  遍历左子节点
            afterPrint(2 * index + 2);
        }
        System.out.println(treeArr[index]);
    }


}

哈夫曼树

基本介绍
  • 给定 n个权值作为 n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)

  • 哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近

重要概念
  • 路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为 L-1

  • 结点的权及带权路径长度

    1.若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权

    2.结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积(W×L)

  • 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和(W1×L1+W2×L2…),记为WPL(weighted pathlength) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。

  • WPL最小的就是哈夫曼树

最优二叉树图解(哈夫曼树)

1、先将数组进行排序

2、然后将数组的前两个最小的元素取出组成左子树和右子树,然后将两者的权值相加组成一个新的二叉树,然后将相加后的权值添加到数组中;

3、然后再次排序,重复上述的1,2操作,直到数组中只剩下最后一个元素

在这里插入图片描述

代码实现:

package test.tree;

import java.util.Collections;
import java.util.LinkedList;

/**
 *@ClassName HafumanBinaryTreeTest
 *@Description 哈夫曼树 -- 案例思路代码整理实现
 * 1.
 *@Author tangjian
 *@Date 2022/3/28 10:30 上午
 *@Version 1.0
 **/
public class HafumanBinaryTreeTest {

    public static void main(String[] args) {
        int[] arr = {16,18,3,4,8,25};
        Node root = createHfmTree(arr);
        root.prePrint();
    }

    public static Node createHfmTree(int[] arr) {
        LinkedList<Node> linkedList = new LinkedList<>();
        for (int i : arr) {
            linkedList.add(new Node(i));
        }
        while (linkedList.size() > 1) {
            //先排序
            Collections.sort(linkedList);
            //取出前两个节点
            Node left = linkedList.getFirst();
            linkedList.removeFirst();
            Node right = linkedList.getFirst();
            linkedList.removeFirst();
            Node newNode = new Node(left.value + right.value);
            newNode.left = left;
            newNode.right = right;
            linkedList.add(newNode);
        }
        return linkedList.getFirst();
    }
}

class Node implements Comparable<Node> {
    Node left; //左子节点
    Node right; //右子节点
    int value; //权值

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

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

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


    public void prePrint() { //前序遍历打印
        System.out.println(this); //先遍历当前自己
        if (this.left != null) {
            this.left.prePrint();
        }
        if (this.right != null) {
            this.right.prePrint();
        }
    }
}
哈夫曼编码

java中的ACSII码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JaaMaVFo-1655521380663)(https://imgblog.csdnimg.cn/img_convert/50bf91181420e53c0605f94715f8061d.png)]

将哈夫曼树的左子树的路径设置为0,将哈夫曼树的右子树的路劲设置为1

代码实现如下:

package test.tree;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;


/**
 *@ClassName HafumanCodeTest
 *@Description 哈夫曼编码生成
 * 1. 将字符串转成byte数组,统计每个Code的value权重和字节c
 * 2. 转成哈夫曼树
 * 3. 然后递归获取哈夫曼编码
 *@Author tangjian
 *@Date 2022/3/31 5:10 下午
 *@Version 1.0
 **/
public class HafumanCodeTest {

    public static Map<Byte, String> hfmCodeMap = new HashMap<>();

    public static void main(String[] args) {
        Map<Byte, Integer> fine = tranToByteArray("i am am am fine");
        System.out.println("字符串fine为:");
        System.out.println(fine);
        Code code = toCreateHfmTree(fine);
        System.out.println("哈夫曼树的前序遍历为:");
        code.prePrint();
        HashMap<Byte, String> map = new HashMap<>();
        getHfmCode(code, "", new StringBuffer(), map);
        System.out.println("该字符串对应的哈夫曼编码集为:");
        System.out.println(map);
    }

    public static Map<Byte, Integer> tranToByteArray(String source) {
        byte[] bytes = source.getBytes();
        Map<Byte, Integer> map = new HashMap<>();
        for (byte b : bytes) {
            if (map.containsKey(b)) {
                map.put(b, map.get(b) + 1);
            } else {
                map.put(b, 1);
            }
        }
        return map;
    }

    public static Code toCreateHfmTree(Map<Byte, Integer> map) {
        LinkedList<Code> codes = new LinkedList<>();
        Iterator<Map.Entry<Byte, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Byte, Integer> next = iterator.next();
            codes.add(new Code(next.getKey(), next.getValue()));
        }
        while (codes.size() > 1) {
            //先排序
            Collections.sort(codes);
            //取出前两个节点
            Code left = codes.getFirst();
            codes.removeFirst();
            Code right = codes.getFirst();
            codes.removeFirst();
            Code newCode = new Code(null, left.value + right.value);
            newCode.left = left;
            newCode.right = right;
            codes.add(newCode);
        }
        return codes.getFirst();
    }

    //递归获取哈夫曼编码集
    public static void getHfmCode(Code code, String dir, StringBuffer str, Map<Byte, String> map) {
        StringBuffer strBuf = new StringBuffer(str);
        strBuf.append(dir);
        if (code != null) {
            if (code.c == null) {
                getHfmCode(code.left, "0", strBuf, map);
                getHfmCode(code.right, "1", strBuf, map);
            } else {
                map.put(code.c, strBuf.toString());
            }
        }
    }
}

class Code implements Comparable<Code> {
    Code left; //左子节点
    Code right; //右子节点
    int value; //权值
    Byte c;

    public Code(Byte c, int value) {
        this.value = value;
        this.c = c;
    }

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

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


    public void prePrint() { //前序遍历打印
        System.out.println(this); //先遍历当前自己
        if (this.left != null) {
            this.left.prePrint();
        }
        if (this.right != null) {
            this.right.prePrint();
        }
    }

}

打印如下:

字符串fine为:
{32=4, 97=3, 101=1, 102=1, 105=2, 109=3, 110=1}
哈夫曼树的前序遍历为:
Node{value=15, c=null}
Node{value=6, c=null}
Node{value=3, c=109}
Node{value=3, c=null}
Node{value=1, c=110}
Node{value=2, c=105}
Node{value=9, c=null}
Node{value=4, c=32}
Node{value=5, c=null}
Node{value=2, c=null}
Node{value=1, c=101}
Node{value=1, c=102}
Node{value=3, c=97}
该字符串对应的哈夫曼编码集为:
{32=10, 97=111, 101=1100, 102=1101, 105=011, 109=00, 110=010}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@晴天_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值