数据结构与算法(八)(树)

数组,链式,树结构的优缺点分析

数组:通过下标访问元素速度快,但是如果检索某个值或者插入值会导致数组的整体移动,效率较低
链式:插入数值或者删除数值效率较高,而在检索时效率较低
树结构:提高数据读取,存储效率,既可以保障数据的检索速度,同时也可以保障数据的插入,删除,修改的速度

关于树的一些概念

节点
根节点
父节点
子节点
叶子节点:没有子节点的节点
节点的权:节点值
满二叉树:所有叶子节点都在最后一层,并且节点总数为2的n次方-1,n为层数
在这里插入图片描述
完全二叉树:所有叶子节点都在最后一层或者倒数第二层,并且最后一层的叶子节点在左面连续,倒数第二层在右面连续
在这里插入图片描述

二叉树的前序,中序,后序遍历

1.概念描述:
前序遍历:先输出父节点,再遍历左子树,最后右子树
中序遍历:先遍历左子树,再输出父节点,最后右子树
后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
2.思路分析
前序遍历:先输出当前节点,如果左子节点不为空,就递归前序遍历,如果右子节点不为空,就递归前序遍历
中序遍历:如果左子节点不为空,就递归中序遍历,输出当前节点,如果右子节点不为空,就递归中序遍历
后序遍历:如果左子节点不为空,就递归后序遍历,如果右子节点不为空,就递归后序遍历,输出当前节点
3.代码实现

package tree;

public class BinaryTreeDemo {
    public static void main(String[] args) {
        StudentNode root = new StudentNode(1,"qq");
        StudentNode node2 = new StudentNode(2,"ww");
        StudentNode node3 = new StudentNode(3,"ee");
        StudentNode node4 = new StudentNode(4,"rr");
        StudentNode node5 = new StudentNode(5,"dd");
        BinaryTree binaryTree = new BinaryTree();
        binaryTree.setRoot(root);

        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);

        binaryTree.preOrder();
        binaryTree.infixOrder();
        binaryTree.afterOrder();
    }
}
//二叉树类
class BinaryTree{
    private StudentNode root ;

    public void setRoot(StudentNode root) {
        this.root = root;
    }

    //前序遍历
    public void preOrder(){
        if(this.root != null){
            this.root.preOrder();
        }
    }

    //中序遍历
    public void infixOrder(){
        if(this.root != null){
            this.root.infixOrder();
        }
    }

    //后序遍历
    public void afterOrder(){
        if(this.root != null){
            this.root.afterOrder();
        }
    }
}
//节点类
class StudentNode{
    private int id;
    private String name;
    //左子节点
    private StudentNode left;
    //右子节点
    private StudentNode right;

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

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public StudentNode getLeft() {
        return left;
    }

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

    public StudentNode getRight() {
        return right;
    }

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

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

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if(this.left != null){
            this.left.preOrder();
        }
        if(this.right != null){
            this.right.preOrder();
        }
    }
    //中序遍历
    public void infixOrder(){
        if(this.left != null){
            this.left.infixOrder();
        }
        System.out.println(this);
        if(this.right != null){
            this.right.infixOrder();
        }
    }
    //后序遍历
    public void afterOrder(){
        if(this.left != null){
            this.left.afterOrder();
        }
        if(this.right != null){
            this.right.afterOrder();
        }
        System.out.println(this);
    }
}

二叉树前序,中序,后序查找

1.思路分析
在这里插入图片描述
2.代码实现

//节点类
//前序查找
    public StudentNode preSearch(int id){
        System.out.println("前序查找");
        if(this.id == id){
            return this;
        }
        StudentNode sn = null;
        if(this.left!=null){
            sn = this.left.preSearch(id);
        }
        if(sn != null){
            return sn;
        }
        if(this.right!=null){
            sn = this.right.preSearch(id);
        }
        return sn;
    }

    //中序查找
    public StudentNode midSearch(int id){
        StudentNode sn = null;
        if(this.left!=null){
            sn = this.left.midSearch(id);
        }
        if(sn != null){
            return sn;
        }
        System.out.println("中序查找");
        if(this.id == id){
            return this;
        }
        if(this.right!=null){
            sn = this.right.midSearch(id);
        }
        return sn;
    }

    //后序查找
    public StudentNode afterSearch(int id){
        StudentNode sn = null;
        if(this.left!=null){
            sn = this.left.afterSearch(id);
        }
        if(sn != null){
            return sn;
        }
        if(this.right!=null){
            sn = this.right.afterSearch(id);
        }
        if(sn != null){
            return sn;
        }
        System.out.println("后序查找");
        if(this.id == id){
            return this;
        }
        return sn;
    }
//二叉树类
//前序查找
    public StudentNode preSearch(int id){
        if(this.root!=null){
            return  this.root.preSearch(id);
        }else {
            return null;
        }
    }
    //中序查找
    public StudentNode midSearch(int id){
        if(this.root!=null){
            return  this.root.midSearch(id);
        }else {
            return null;
        }
    }
    //后序查找
    public StudentNode afterSearch(int id){
        if(this.root!=null){
            return  this.root.afterSearch(id);
        }else {
            return null;
        }
    }

二叉树的删除

1.规定:如果是叶子节点直接删除,如果是非叶子节点,就删除该子树
2.思路分析:因为二叉树是单向的,所以需要判断当前节点的子节点是否需要删除
如果当前节点的左子节点存在,并且就是要删除的节点,就将当前节点的左子节点设置为空
如果当前节点的右子节点存在,并且就是要删除的节点,就将当前节点的右子节点设置为空
如果以上两步都没有找到需要删除的节点,那么就向左递归
如果以上三步都没有找到需要删除的节点,那么就向右递归
注意:需要先判断root节点是否是需要删除的节点
3.代码实现

//节点类
public void delete(int id){
        if(this.left!=null && this.left.id == id){
            this.left = null;
            return;
        }
        if(this.right!=null && this.right.id == id){
            this.right = null;
            return;
        }
        if(this.left!=null){
            this.left.delete(id);
        }
        if(this.right!=null){
            this.right.delete(id);
        }
    }
//删除节点
    public void delete(int id){
        if(root.getId() == id){
            root = null;
        }else{
            root.delete(id);
        }
    }

顺序存储二叉树

1.概念:数组存储方式可以与树存储方式相互转换,要求遍历数组时依然可以实现前序,中序,后序遍历
在这里插入图片描述
2.特点描述:通常只考虑完全二叉树,第n个元素的左子节点为2n+1,第n个元素的右子节点为2n+2,第n个元素的父节点为(n-1)/2,n表示二叉树中的第几个元素(从0开始)
3.代码实现

package tree;

public class ArrayBinaryTree {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        ArrayTree at = new ArrayTree(arr);
        at.preOrder();
    }
}
class ArrayTree{
    private int[] arr;

    public ArrayTree(int[] arr) {
        this.arr = arr;
    }

    public void preOrder(){
        preOrder(0);
    }

    //前序遍历
    public void preOrder(int index){
        if(arr == null || arr.length == 0){
            System.out.println("数组为空");
        }
        System.out.println(arr[index]);
        if((index*2+1) < arr.length){
            preOrder(index*2+1);
        }
        if((index*2+2) < arr.length){
            preOrder(index*2+2);
        }
    }
}

线索化二叉树

1.简介:有二叉树
在这里插入图片描述
此时中序遍历结果为:8,3,10,1,14,6,此时8,10,14的左右节点都为空,6的右节点为空,我们希望充分利用这些节点,所以我们让8的左子节点指向前驱节点(null),让8的右子节点指向后继节点(3),以此类推
2.问题注意:当你根据1的操作完成线索化二叉树后,有以下问题,left连接的可能是左子树,也有可能是前驱节点,right连接的可能是右子树,也有可能是后继节点,需要一个变量来区分
3.代码实现

package tree.threadedBinaryTree;
public class ThreadedBinaryTree {
    public static void main(String[] args) {
        StudNode root = new StudNode(1,"11");
        StudNode sn1 = new StudNode(3,"33");
        StudNode sn2 = new StudNode(6,"66");
        StudNode sn3 = new StudNode(8,"88");
        StudNode sn4 = new StudNode(10,"1010");
        StudNode sn5 = new StudNode(14,"1414");

        root.setLeft(sn1);
        root.setRight(sn2);
        sn1.setLeft(sn3);
        sn1.setRight(sn4);
        sn2.setLeft(sn5);

        ThreadedBinary tb = new ThreadedBinary();
        tb.setRoot(root);
        tb.threadedTree(root);

        System.out.println(sn2.getLeft());
        System.out.println(sn2.getRight());
    }
}

//二叉树类
class ThreadedBinary{
    private StudNode root ;
    private StudNode pre = null;

    public void setRoot(StudNode root) {
        this.root = root;
    }

    //线索化二叉树
    public void threadedTree(StudNode sn){
        //中序线索化二叉树
        if(sn.getLeft() != null){
            threadedTree(sn.getLeft());
        }
        if(sn.getLeft()==null){
            sn.setLeft(pre);
            sn.setLeftType(1);
        }
        if(pre!=null && pre.getRight()==null){
            pre.setRight(sn);
            pre.setRightType(1);
        }
        pre=sn;
        if(sn.getRight() != null){
            threadedTree(sn.getRight());
        }
    }

    //前序遍历
    public void preOrder(){
        if(this.root != null){
            this.root.preOrder();
        }
    }

    //中序遍历
    public void infixOrder(){
        if(this.root != null){
            this.root.infixOrder();
        }
    }

    //后序遍历
    public void afterOrder(){
        if(this.root != null){
            this.root.afterOrder();
        }
    }
}
//节点类
class StudNode{
    private int id;
    private String name;
    //左子节点
    private StudNode left;
    //右子节点
    private StudNode right;
    //为线索二叉树做准备
    //左右节点的状态,0表示连接的是左子节点或者右子节点,1表示连接的是前驱节点或者后继节点
    private int leftType;
    private int rightType;

    public int getLeftType() {
        return leftType;
    }

    public void setLeftType(int leftType) {
        this.leftType = leftType;
    }

    public int getRightType() {
        return rightType;
    }

    public void setRightType(int rightType) {
        this.rightType = rightType;
    }

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

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public StudNode getLeft() {
        return left;
    }

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

    public StudNode getRight() {
        return right;
    }

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

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

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if(this.left != null){
            this.left.preOrder();
        }
        if(this.right != null){
            this.right.preOrder();
        }
    }
    //中序遍历
    public void infixOrder(){
        if(this.left != null){
            this.left.infixOrder();
        }
        System.out.println(this);
        if(this.right != null){
            this.right.infixOrder();
        }
    }
    //后序遍历
    public void afterOrder(){
        if(this.left != null){
            this.left.afterOrder();
        }
        if(this.right != null){
            this.right.afterOrder();
        }
        System.out.println(this);
    }
}

4.中序线索化二叉树遍历代码实现

 //遍历中序线索化二叉树
    public void midOrderThreaded(){
        StudNode sn = root;
        while(sn != null){
            //一直向左找,直到找到8为止
            while(sn.getLeftType()==0){
                sn = sn.getLeft();
            }
            System.out.println(sn);
            //只要是后继节点就继续输出
            while(sn.getRightType()==1){
                sn = sn.getRight();
                System.out.println(sn);
            }
            sn = sn.getRight();
        }
    }

赫夫曼树

1.基本概念介绍:
路径:在一棵树中,从一个节点往下可以达到的孩子或者孙子节点之间的通路
路径长度:通路中分支的数目
节点的权:若将树中节点赋给一个有着某种含义的数值,那么这个数值称之为节点的权
带权路径长度:从根节点到该节点之间的路径长度与该节点的权的乘积
树的带权路径长度:所有叶子节点的带权路径长度之和,记为WPL,权值越大的节点距离根节点越近的二叉树才是最优二叉树,WPL最小的就是赫夫曼树
2.赫夫曼树思路分析:
假如有数列{13,7,8,3,29,6,1}形成赫夫曼树
a.先将该数列从小到大排序,将每一个数字看成一个节点,把每个节点看成一个二叉树
b.取出根节点权值最小的两棵二叉树
c.组成一个新的二叉树,该新二叉树的根节点的权值是前面两个二叉树根节点权值的总和
d.再将新二叉树,在次排序,重复上述过程,直到该数列只剩下一个值,此时赫夫曼树成功
3.代码实现

package huffmantree;

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

public class HuffmanTree {
    public static void main(String[] args) {
        int[] arr = {13,7,8,3,29,6,1};
        Node root = huffManTree(arr);
        preNode(root);
    }

    //生成赫夫曼树
    public static Node huffManTree(int[] arr){
        List<Node> list = new ArrayList<Node>();
        for(int val : arr){
            list.add(new Node(val));
        }
        while(list.size()>1){
            //排序
            Collections.sort(list);
            //取最小的两个
            Node left = list.get(0);
            Node right = list.get(1);
            //生成新的
            Node nNode = new Node(left.value+right.value);
            nNode.left = left;
            nNode.right = right;
            list.remove(left);
            list.remove(right);
            list.add(nNode);
        }
        return  list.get(0);
    }

    //前序遍历
    public static void preNode(Node root){
        root.preNode();
    }
}

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

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

    public void preNode(){
        System.out.println(this);
        if(this.left!=null){
            this.left.preNode();
        }
        if(this.right!=null){
            this.right.preNode();
        }
    }

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

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

赫夫曼编码

1.几种编码方式的区别:
定长编码:将字符转换成为二进制码来传递数据,这样效率比较低
变长编码:将字符串中每个字母的数量查询出来,根据数量去转换成二进制码,这样效率较高,但是容易产生二义性
赫夫曼编码:使用赫夫曼树进行编码,将字符串中每个字母的数量查询出来,生成赫夫曼树,根据赫夫曼树生成二进制码,这样效率高,并且没有二义性
2.思路分析:
a.首先将字符串中的每个字母的数量查出来
b.按照上面查出的字母数量生成一个赫夫曼树,权值就是次数
c.根据赫夫曼树,给每一个字符确定编码,这里规定向左为0,向右为1
d.根据赫夫曼编码对字符串进行编码
3.使用赫夫曼编码进行数据压缩和解压
思路:首先将字符串1中的每个字母的数量查出来,之后生成赫夫曼树,得到赫夫曼编码,将字符串1通过赫夫曼编码变成字符串2,再将字符串2 8个一组变成byte,放入byte数组,完成压缩,解压时,先将byte数组变成字符串2,在通过赫夫曼编码将字符串2变成字符串1,完成解压
4.代码实现

package huffmantree;

import sun.rmi.server.InactiveGroupException;

import java.io.*;
import java.util.*;

public class HuffmanCode {
    public static void main(String[] args) {
        String str = "i like like like java do you like a java cde";
        byte[] bytes = str.getBytes();
        Byte[] bytes1 = zip(bytes);
        System.out.println(Arrays.toString(bytes1));
        byte[] bs = decode(mapCodes,bytes1);
        System.out.println("解压之后"+new String(bs));
        zipFile("d://112.png","d://224.zip");
        upZipFile("d://224.zip","d://113.png");
    }

    //解压缩文件
    public static void upZipFile(String fileZip,String file){
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(fileZip);
            ois = new ObjectInputStream(fis);
            Byte[] bs = (Byte[]) ois.readObject();
            Map<Byte,String > map = (Map<Byte, String>) ois.readObject();
            byte[] b = decode(map,bs);
            fos = new FileOutputStream(file);
            fos.write(b);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                fis.close();
                ois.close();
                fos.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    //压缩文件
    public static void zipFile(String file,String fileZip){
        FileInputStream is = null;
        FileOutputStream os = null;
        ObjectOutputStream oos = null;
        try {
            is = new FileInputStream(file);
            byte[] bs = new byte[is.available()];
            is.read(bs);

            Byte[] b = zip(bs);

            os = new FileOutputStream(fileZip);
            oos = new ObjectOutputStream(os);
            oos.writeObject(b);
            oos.writeObject(mapCodes);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                is.close();
                os.close();
                oos.close();
            }catch (Exception e){

            }
        }
    }

    //解压缩
    public static byte[] decode(Map<Byte,String> map,Byte[] bytes){
        //将压缩后的数组变成字符串
        StringBuilder stringBuilder = new StringBuilder();
        for(int i=0;i<bytes.length;i++){
            stringBuilder.append(byteToBitString(bytes[i]));
        }
        stringBuilder.append(last);
        //将字符串翻译成为byte数组
        Map<String,Byte> map1 = new HashMap<String,Byte>();
        for(Map.Entry<Byte,String> entry:map.entrySet()){
            map1.put(entry.getValue(),entry.getKey());
        }
        List<Byte> list = new ArrayList<>();
        for(int i =0;i<stringBuilder.length();){
            int count = 1;
            boolean flag = true;
            Byte b =null;
            while(flag){
                String str = stringBuilder.substring(i,i+count);
                b = map1.get(str);
                if(b == null){
                    count++;
                }else{
                    flag = false;
                }
            }
            list.add(b);
            i+=count;
        }
        byte[] bs = new byte[list.size()];
        for(int i =0;i<list.size();i++){
            bs[i] = list.get(i);
        }
        return bs;
    }

    //将一个byte转换成字符串
    public static String byteToBitString(byte b){
        int temp = b;
        temp |= 256;
        String str = Integer.toBinaryString(temp);
        return  str.substring(str.length()-8);
    }

    //压缩
    public static Byte[] zip(byte[] bytes){
        //整理byte数组
        List<Code> list = toList(bytes);
        //生成赫夫曼树
        Code root = toHuffManTree(list);
        //按照赫夫曼树得到赫夫曼编码
        toHuffmanCode(root,"",stringBuilder);
        //得到压缩后的数组
        Byte[] bytes1 = tobyteArrays(bytes,mapCodes);
        return  bytes1;
    }

    //通过赫夫曼编码翻译str,再将翻译过后的字符串8位一组变成byte[]
    public static Byte[] tobyteArrays(byte[] bytes,Map<Byte,String> mapCodes){
        //将bytes通过赫夫曼编码生成字符串
        StringBuilder stringBuilder = new StringBuilder();
        for(byte by : bytes){
            stringBuilder.append(mapCodes.get(by));
        }
        //将字符串8位一组再变成一个新得byte[]起到压缩的目的
        int len;
        if(stringBuilder.length()%8 == 0){
            len = stringBuilder.length()/8;
        }else{
            len = stringBuilder.length()/8+1;
        }
        Byte[] returnBytes = new Byte[len-1];
        String byteString = null;
        int index = 0;
        for(int i=0;i<stringBuilder.length();i+=8){
            if(i+8>stringBuilder.length()){
                byteString = stringBuilder.substring(i);
                last = byteString;
            }else{
                byteString = stringBuilder.substring(i,i+8);
                returnBytes[index] = (byte)Integer.parseInt(byteString,2);
                index ++;
            }
        }
        return returnBytes;
    }

    //从赫夫曼树中获取赫夫曼编码
    static Map<Byte,String> mapCodes = new HashMap<>();
    static StringBuilder stringBuilder = new StringBuilder();
    static String last = new String();
    public static Map<Byte,String> toHuffmanCode(Code code,String string ,StringBuilder stringBuilder){
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        stringBuilder2.append(string);
        if(code != null){
            if(code.byt == null){
                //向左递归
                toHuffmanCode(code.left,"0",stringBuilder2);
                //向右递归
                toHuffmanCode(code.right,"1",stringBuilder2);
            }else{
                mapCodes.put(code.byt,stringBuilder2.toString());
            }
        }
        return mapCodes;
    }

    //前序遍历
    public static void preOrder(Code root){
        if(root == null){
            return;
        }else{
            root.preOrder();
        }
    }
    //生成赫夫曼树
    public static Code toHuffManTree(List<Code> list){
        while(list.size() > 1){
            Collections.sort(list);
            Code left = list.get(0);
            Code right = list.get(1);
            Code nCode = new Code(null,left.times+right.times);
            nCode.left = left;
            nCode.right =right;
            list.remove(left);
            list.remove(right);
            list.add(nCode);
        }
        return list.get(0);
    }
    //整理数据,方便生成赫夫曼树
    public static List<Code> toList(byte[] bytes){
        Map<Byte,Integer> map = new HashMap<Byte,Integer>();
        List<Code> list = new ArrayList<Code>();
        for(int i=0;i<bytes.length;i++){
            if(map.get(bytes[i]) != null){
                map.put(bytes[i],map.get(bytes[i])+1);
            }else {
                map.put(bytes[i],1);
            }
        }
        for(Map.Entry<Byte,Integer> entry : map.entrySet()){
            list.add(new Code(entry.getKey(),entry.getValue()));
        }
        return list;
    }
}

class Code implements Comparable<Code>{
    Byte byt;
    int times;
    Code left;
    Code right;

    public Code(Byte byt, int times) {
        this.byt = byt;
        this.times = times;
    }

    @Override
    public String toString() {
        return "Code{" +
                "byt=" + byt +
                ", times=" + times +
                '}';
    }

    //排序
    @Override
    public int compareTo(Code o) {
        return this.times - o.times;
    }

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if(this.left != null){
            this.left.preOrder();
        }
        if(this.right != null){
            this.right.preOrder();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值