树的基本术语、二叉树、搜索树、红黑树、B+树

281 阅读7分钟

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

一、树的基础知识快速了解:

1.1树的基本特点

在这里插入图片描述 如上图所示:

  • 有一个根节点,一般称为root节点
  • 每一个元素都被称为node
  • 除了root节点外,其余的节点都会被分成n个互不相交的集合,子树。递归

1.2 树的基本术语

  • 结点:树形结构里面的元素
  • 子树:当结点大于1时,其余的结点分为的互不相交的集合称为子树
  • 度:一个结点拥有的子树数量称为结点的度
  • 叶子:度为0的结点
  • 孩子:结点的子树的根称为孩子结点
  • 双亲:和孩子结点对应
  • 兄弟:同一个双亲结点
  • 深度:结点的最大层次称为树的深度,计算时间复杂度用
  • 森林:由N个互不相交的树构成深林

二、最常用的树-----二叉树

2.1 二叉树 的定义:

  • 每个节点至多只有两颗子树

在这里插入图片描述 如上图所示,2,3,4是二叉树,1不是。

  • 二叉树和满二叉树(也叫平衡二叉树如图三所示)的特点: 满二叉树共有的节点树:2^n-1,n为满二叉树的深度,如图三,共有3层,深度为3,n=3;套入公式得节点树为7。 满二叉树的第n层的节点个数是2^(n-1) 二叉树,可能不是满二叉树,所以节点是小于2^n-1的

2.2二叉树的前中后序遍历

在这里插入图片描述

  • 前序遍历(根、左、右): A、B、C、D、E、F、G、H、I
  • 中序遍历(左、根、右): C、B、D、A、F、E、H、G、I -后序遍历(左、右、根): C、D、B、F、H、I、G、E、A

2.3 代码实现搜索二叉树

在这里插入图片描述 如上图所示:中序遍历是有序的才是一颗搜索二叉树 1、2、3、4、5、6、7、8、9

以下代码完成了一刻搜索二叉树的构建、提供了一个中序遍历的方法、查询任意一个节点在不在构建的搜索二叉树中。搜索二叉树的查找效率很高,为 o(logn)(从根节点找,比根节点小,就从左边找,比根节点大,就从右边找)。

/**
 * @author liujun
 * 手撕一颗搜索二叉树(中序遍历的时候,能从小到大排列)
 */
public class BuildBinarySearchTree {
    private int data;
    private BuildBinarySearchTree left;
    private BuildBinarySearchTree right;

    public BuildBinarySearchTree(int data) {
        this.data = data;
    }

    public BuildBinarySearchTree getLeft() {
        return left;
    }

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

    public BuildBinarySearchTree getRight() {
        return right;
    }

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

    //提供构建一颗搜索二叉树(中序遍历的时候必须是有序的)方法
    public static void insert(BuildBinarySearchTree root,int data){
        //新插入的节点比根节点大,则放入根结点的右边
        if (root.data<data){
            if (root.getRight()==null){
                BuildBinarySearchTree buildBinarySearchTree = new BuildBinarySearchTree(data);
                root.setRight(buildBinarySearchTree);
            }else {
                insert(root.getRight(),data);
            }
        }else if (root.data>data){
            //新插入的节点比根节点大,则放入根结点的左边
            if (root.getLeft()==null){
                BuildBinarySearchTree buildBinarySearchTree = new BuildBinarySearchTree(data);
                root.setLeft(buildBinarySearchTree);
            }else {
                insert(root.getLeft(),data);
            }
        }
    }


    //提供一个中序遍历的方法
    static void in(BuildBinarySearchTree root){
        if (root.getLeft()!=null)
        in(root.getLeft());
        System.out.println(root.data);
        if (root.getRight()!=null)
        in(root.getRight());
    }
    static int findCount = 0;
    //提供一个查找的方法
    static void findData(BuildBinarySearchTree root,int searchData){
        try {
            if (root.data<searchData){
                findCount++;
                findData(root.getRight(),searchData);
            }else if(root.data>searchData){
                findCount++;
                findData(root.getLeft(),searchData);
            }else {
                System.out.println("一共查找了:"+findCount+"次"+"找到的结点是:"+ root+"  找的数是:"+ root.data);
            }
        }catch (NullPointerException e){
            System.out.println("该二叉搜索树中没有查找到...");
        }

    }


    public static void main(String[] args) {
        int[] data = {5,4,7,3,1,9,6,2,8};
        BuildBinarySearchTree buildBinarySearchTree = new BuildBinarySearchTree(data[0]);

        for (int i = 1; i < data.length; i++) {
            insert(buildBinarySearchTree,data[i]);
        }

        //遍历输出,看搜索二叉树是否排列好
        in(buildBinarySearchTree);

        //从构建好的搜索二叉树查询某个数是否存在二叉树之中
        findData(buildBinarySearchTree,3);

    }
}

执行结果
1
2
3
4
5
6
7
8
9
一共查找了:2次找到的结点是:com.jack.tree.BuildBinarySearchTree@6d6f6e28  找的数是:3

2.4 最优二叉树----哈夫曼树

要求带权重路径的总和最短 在这里插入图片描述 (a):72+52+22+42=36 (b):73+53+21+42=46 (c):71+52+23+43=35

哈夫曼树在战争期间常被用来做电报的加解密树 如c所示 a----编码后-->1 b----编码后-->001 c----编码后-->000 d----编码后--->0001

代码实现哈夫曼树

/**
 * @author liujun
 * 实现一颗哈夫曼树,并提供加解密方法
 */
class MyNode implements Comparable<MyNode>{
    private String data;
    private int weight;
    private MyNode parentNode;
    private MyNode leftNode;
    private MyNode rightNode;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public MyNode getParentNode() {
        return parentNode;
    }

    public void setParentNode(MyNode parentNode) {
        this.parentNode = parentNode;
    }

    public MyNode getLeftNode() {
        return leftNode;
    }

    public void setLeftNode(MyNode leftNode) {
        this.leftNode = leftNode;
    }

    public MyNode getRightNode() {
        return rightNode;
    }

    public void setRightNode(MyNode rightNode) {
        this.rightNode = rightNode;
    }

    @Override
    public int compareTo(MyNode o) {
        return this.weight>o.weight?1:-1;
    }
}


public class HumanTree {
    private MyNode root;
    private List<MyNode> leafNodeList;
    private Map<Character,Integer> weightMap;

    public HumanTree(Map<Character, Integer> weightMap) {
        this.leafNodeList = new ArrayList<>();
        this.weightMap = weightMap;
    }

    //构建树
    public void createTree(){
        //使用优先队列从小到大排列
        PriorityBlockingQueue<MyNode> myNodeQueue = new PriorityBlockingQueue<>();
        Set<Character> characters = weightMap.keySet();
        for (Character character : characters) {
            MyNode myNode = new MyNode();
            myNode.setData(character+"");
            myNode.setWeight(weightMap.get(character));
            myNodeQueue.add(myNode);
            leafNodeList.add(myNode);
        }
        //合
        for (int i = 0; i < leafNodeList.size()-1; i++) {
            //取出最小的两个做叶子节点
            MyNode leftNode = myNodeQueue.poll();
            MyNode rightNode = myNodeQueue.poll();
            MyNode parentNode = new MyNode();
            //断言
            assert leftNode != null;
            assert rightNode != null;
            parentNode.setData(leftNode.getData()+rightNode.getData());
            parentNode.setLeftNode(leftNode);
            parentNode.setRightNode(rightNode);
            leftNode.setParentNode(parentNode);
            rightNode.setParentNode(parentNode);
            myNodeQueue.add(parentNode);
        }
        //队列的最后一个作为整颗哈夫曼数的根节点
        root=myNodeQueue.poll();
        System.out.println("哈夫曼树构建完成");
    }

    //编码
    public Map<String,String> encode(){
        Map<String,String> encodeMap = new HashMap<>();
        for (MyNode currentNode : leafNodeList) {
             MyNode temp  =  currentNode;
            String code = "";
            while (currentNode.getParentNode() != null) {
                if (currentNode.getParentNode().getLeftNode() == currentNode) {
                    code = 0 + code;
                    currentNode = currentNode.getParentNode();
                } else {
                    code = 1 + code;
                    currentNode = currentNode.getParentNode();
                }
            }
            encodeMap.put(temp.getData(), code);
        }
        return encodeMap;
    }

    //解码
    Map<String,String> decodeMap =new LinkedHashMap<>();
    public Map<String,String> decode(List<String> encodeList){
        for (String encode : encodeList) {
            MyNode currentNode = new MyNode();
            MyNode localRoot = root;
            for (int i = 0; i < encode.length(); i++) {
                char c = encode.charAt(i);
                if (c == '0'&& localRoot.getLeftNode()!=null){
                       currentNode = localRoot.getLeftNode();
                       localRoot = localRoot.getLeftNode();
                }else if (c=='1' && localRoot.getRightNode()!=null){
                    currentNode = localRoot.getRightNode();
                    localRoot = localRoot.getRightNode();
                }else {
                    //如果在最后一个 字节未找到该节点证明在哈夫曼树中没有对应的解密报文
                    System.out.println("未找到该节点的解密报文"+encode);
                }
            }
            //找到了就把密文和解密报文存入map
            decodeMap.put(encode,currentNode.getData());
        }
        return decodeMap;
    }

    public static void main(String[] args) {
        //定义报文和报文的权重
        Map<Character,Integer> weightMap = new HashMap<>();
        weightMap.put('A',7);
        weightMap.put('B',5);
        weightMap.put('C',2);
        weightMap.put('D',4);
      	weightMap.put('E',8);
        HumanTree humanTree = new HumanTree(weightMap);
        //构建哈夫曼树
        humanTree.createTree();
        //
        Map<String, String> encode = humanTree.encode();
        List<String> encodeList = new ArrayList<>();
        //遍历加密
        System.out.println("编码");
        for (String s : encode.keySet()) {
            System.out.println(s+"-->"+encode.get(s));
            encodeList.add(encode.get(s));
        }
        //遍历解密

        System.out.println("-----------");
        System.out.println("解码");
        Map<String, String> decodeMap = humanTree.decode(encodeList);
        for (String s : decodeMap.keySet()) {
            System.out.println(s+"---->"+decodeMap.get(s));
        }
    }


}


//运行结果
哈夫曼树构建完成
编码
A-->01
B-->001
C-->0000
D-->0001
E-->1
-----------
解码
01---->A
001---->B
0000---->C
0001---->D
1---->E

2.5 代码实现Collections.sort()底层用到的合并排序

在这里插入图片描述 如上图所示,把一个数组,不断拆分为最小的粒度,然后再排序、合并 代码实现:


/**
 * 模拟了合并排序
 */
public class MergeSort {



    public static void mergeSort(int[] data, int left, int right) {
        if (left < right) {
            //分
            //递归二分化为最小单位
            int mid = (left + right) / 2;
            mergeSort(data, left, mid);
            mergeSort(data, mid + 1, right);
            //合
            merge(data,left,mid,right);
        }
    }

    public static void merge(int[] data,int left,int mid,int right){
        //定义一个临时的数组
        int[] temp = new int[data.length];

        //定义两个指针,记录要比较数,左边第一个
        int point1 = left;
        int point2 = mid+1;
        //定义一个临时的下标,记录插入temp数组的角标
        int local = left;

        //小的数插入临时数组的左边
        while (point1<=mid && point2 <=right){
            if (data[point1]<data[point2]){
                temp[local] = data[point1];
                point1++;
                local++;
            }else {
                temp[local] = data[point2];
                point2++;
                local++;
            }
        }

        //防止左边还有的数没有插入
        while (point1<=mid){
            temp[local++] = data[point1++];
        }

        //防止右边还有的数没有插入
        while (point2<=right){
            temp[local++] = data[point2++];
        }

        //把临时数组的值赋值给原数组
        if (right + 1 - left >= 0) System.arraycopy(temp, left, data, left, right + 1 - left);


    }

    public static void main(String[] args) {
        int[] data = {1, 3, 4, 5, 63, 2, 4, 6, 9, 10,-1};
        mergeSort(data,0,data.length-1);
        System.out.println(Arrays.toString(data));

    }

}



//运行结果
[-1, 1, 2, 3, 4, 4, 5, 6, 9, 10, 63]

Process finished with exit code 0

2.6代码实现hashMap

(红黑树那里未实现)

/**
 * 实现hashMap中数组加链表的数据结构
 * @param <K>
 * @param <V>
 */
class Entry<K, V> {
    //键
    public K key;
    //值
    public V value;
    //模拟hash冲突的时候,链表的数据结构
    public Entry<K, V> next;
    //hash冲突的个数
    public int cap;

    public Entry(K key, V value, Entry<K, V> next) {
        this.key = key;
        this.value = value;
        this.next = next;
    }
}


public class MyHashMap<K, V> {
    //MyHashMap的默认容量
    private static final int Default_size = 1 << 4;
    //模拟hashMap的数组结构
    private Entry<K, V>[] data;
    //用户自定义的hashMap容量
    private int capacity;
    //记录数组已添加了的entry个数,方便0.75f扩容
    private int size;

    //提供无参构造方法和有参构造法
    public MyHashMap() {
        this(Default_size);
    }


    public MyHashMap(int capacity) {
        //判断用户传入的容量
        if (capacity <= 0) {
            this.capacity = Default_size;
        } else {
            this.capacity = capacity;
        }
        data = new Entry[capacity];
    }

    //提供一个put方法
    public void put(K key, V value) {
        //先对传入的key进行hash
        int hashKey = hash(key);
        //新建一个entry
        Entry<K, V> newEntry = new Entry<>(key, value, null);
        Entry<K, V> entry = data[hashKey];
        //发生了hash冲突,比较链表的key
        while (entry != null) {
            if (entry.key.equals(key)) {
                //键相同,值覆盖
                entry.value = value;
                //下面的操作都不执行了
                return;
            }
            //不相等的话遍历链表,直到找到最后一个entry为null,那么entry下移,头插新的entry
            entry = entry.next;
        }
        newEntry.next = entry;
        data[hashKey] = newEntry;
        size++;
    }

    //提供一个get方法
    public V get(K key) {
        //hashMap可以允许有一个key为null,这里不模拟那种情况
        if (key != null) {
            int hash = hash(key);
            Entry<K, V> entry = data[hash];
            while (entry != null) {
                if (entry.key.equals(key)) {
                    return entry.value;
                }
                entry = entry.next;
            }
        }
        return null;
    }

    //引用源码的hash方法
    int hash(Object key) {
        int h = 0;
        if (key == null)
            h = 0;
        else {
            h = key.hashCode() ^ (h >>> 16); // 无符号右移16位
        }
        return h % capacity;
    }

    public static void main(String[] args) {
        MyHashMap hashMap = new MyHashMap();
        hashMap.put("1", "a");
        hashMap.put("2", "b");
        hashMap.put("3", "c");
        hashMap.put("4", "d");
        System.out.println(hashMap.get("1"));
        System.out.println(hashMap.get("3"));
        System.out.println(hashMap.get("5"));
    }
}


//运行结果
a
c
null

Process finished with exit code 0

2.7红黑树的特征:

在这里插入图片描述

  • 每个结点不是红色就是黑色
  • 不可能有连在一起的红色结点
  • 根结点都是黑色
  • 每个红色结点的两个子结点都是黑色
  • 任一结点到其子树中每个叶子节点的路径都有相同数量 的黑色结点

旋转和颜色变换规则: 1.变颜色的情况:当前结点的父亲是红色,且它的祖父结点的另一个子结点 也是红色。(叔叔结点): (1)把父节点设为黑色 (2)把叔叔也设为黑色 (3)把祖父也就是父亲的父亲设为红色 (4)把指针定义到祖父结点设为当前要操作的.

2.左旋:当前父结点是红色,叔叔是黑色的时候,且当前的结点是右子树。左旋 以父结点作为左旋。

3.右旋:当前父结点是红色,叔叔是黑色的时候,且当前的结点是左子树。右旋 (1)把父结点变为黑色 (2)把祖父结点变为红色 (爷爷) (3)以祖父结点旋转(爷爷)

2.8 B+树

在这里插入图片描述 B+树作为MySql的索引树,如图所示,他的分叉是大于2的,子树会根据插入的数据,分裂,上移。之所以MySql不采用二叉树,是因为MySql的数据是储存在磁盘上的,磁盘的分区结构,使用B+树的效率高。取一次磁盘扇区的数据,很大一块数据可以一次性取出来。