数据结构Java--8

二叉搜索树

像上图这样满足,任意一棵子树的左子树小于该子树的根结点,右子树大于该子树的根结点,满足这样的条件,则这种树就被称为二叉搜索树。

java 复制代码
public class BinarySearchTree {
    static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;
        public TreeNode(int val) {
            this.val = val;
        }
    }

    public TreeNode root = null;

    public boolean search(int val) {
        TreeNode cur = root;
        while(cur!=null){
            if(cur.val > val){
                cur = cur.left;
            }else if(cur.val < val) {
                cur = cur.right;
            }else{
                return true ;
            }
        }
        return false;
    }

    public void insert(int val) {
        TreeNode node = new TreeNode(val);
        if(root==null){
            root=node;
            return;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        while(cur!=null){
            parent = cur;
            if(cur.val>val){
                cur=cur.left;
            }else if(cur.val<val){
                cur=cur.right;
            }else{
                return;
            }
        }
        if(parent.val>val){
            parent.left = node;
        }else if(parent.val<val) {
            parent.right = node;
        }
    }

    public void remove(int val){
        if(root==null){
            return;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        while(cur!=null) {
            if(cur.val>val){
                parent=cur;
                cur=cur.left;
            }else if(cur.val<val){
                parent=cur;
                cur=cur.right;
            }else{
                removeNode(parent,cur);
            }
        }
    }

    private void removeNode(TreeNode parent,TreeNode cur) {
        if(cur.left == null) {//以cur为根左树为空的情况
            if(cur==root){//parent和cur在一起(顶根为要删除的节点时)
                root=cur.right;
            }else if(cur==parent.left){//cur为要删除元素并且走到了parent左边,cur左边为空,把parent的左边接上cur的右边
                parent.left=cur.right;
            }else{//cur为要删除元素并且走到了parent右边,cur左边为空,把parent的右边接上cur的右边
                parent.right=cur.right;
            }
        } else if (cur.right == null) {//以cur为根右树为空的情况
            if(cur==root){
                root=cur.left;
            }else if(cur==parent.left){
                parent.left=cur.left;
            }else{
                parent.right=cur.left;
            }
        }else{//cur左右都不为空的情况************
            TreeNode targetParent = cur;
            TreeNode target = cur.left;
            while(target.right!=null){
                targetParent = target;
                target = target.right;
            }
            cur.val=target.val;
            if(target==targetParent.right){
                targetParent.right=target.left;
            }else{
                targetParent.left=target.left;
            }
        }
    }
}

二叉搜索树的模拟只要遵循二叉搜索树的特性即可。

单支的二叉搜索树

如果插入的顺序不恰当,二叉搜索树就会出现只有单支的情况

这样也属于二叉搜索树,但是由于另一半枝没有放入元素,我们对该种搜索树进行操作时,效率必然会有所下降。

Map和Set

先了解一下Map和Set的概念,以及两者的区别。

首先这两种集合是高效的搜索容器,很适合动态查找,不同于我们以前学过的其他数据结构,那些数据结构进行动态查找都需要不小的时间复杂度。

Map是一种Key-Value模型,kv模型的意思就是,如果你往里面存放数据,那就需要提供key 和 value的值,我们也称这种模型为键值对模型。例如我们要统计某单词在英语试卷中出现的次数,

<单词,单词出现的次数>对应的就是 Key 和 Value

Set是一种Key模型,如果你往里面存放数据,只需要提供一个数据Key,但是Set不允许有重复的数据,所以Set更偏向于去重,查找是否存在目标数据

TreeMap

TreeMap就是结合了刚刚提到的KeyValue模型和搜索树,实际上TreeMap底层是红黑树,现阶段还没学到红黑树,这里不细说它的结构

java 复制代码
Map<String,Integer> map = new TreeMap<>();
TreeMap<String,Integer> map2 = new TreeMap<>();

创建一个TreeMap有以上两种格式,推荐使用第一种

Map有几个核心的方法

java 复制代码
public static void main(String[] args) {
        Map<String,Integer> map = new TreeMap<>();//key value type
        TreeMap<String,Integer> map2 = new TreeMap<>();
        map.put("b",3);
        map.put("c",2);
        map.put("v",5);
        int val = map.getOrDefault("d",-1);//如果存在key就返回key的value,不存在就返回Default(-1)
        System.out.println(val);
        val = map.get("c");
        System.out.println(val);
        Set<String> set = map.keySet();//收集key
        Collection<Integer> collection=map.values();//收集value
        Set<Map.Entry<String,Integer>> entries = map.entrySet();//搜索树上每一个节点放的都是Map.Entry
        for (Map.Entry<String,Integer> entry :entries) {
            System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
        }
        for (Map.Entry<String,Integer> entry :map.entrySet()) {
            System.out.println(entry.getKey()+entry.getValue());
        }
}

TreeSet

Set是纯Key模型,适合查找存在

java 复制代码
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("a");
        set.add("g");
        set.add("tq");
        set.remove("a");
        boolean flag =set.contains("g");
        System.out.println(flag);
        System.out.println(set);
    }
java 复制代码
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("a");
        set.add("g");
        set.add("tq");
        set.add("a");
//        set.remove("a");
        boolean flag =set.contains("g");
        System.out.println(flag);
        System.out.println(set);
    }

TreeSet的底层也是红黑树

哈希表

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键 码的多次比较。

顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log2(N) ),搜索的效率取决于搜索过程中 元素的比较次数。

但是哈希表不一样,哈希表可以不经过任何比较,一次直接从表中得到要搜索的元素。

冲突

要明白什么是哈希表的冲突之前,不防这样设想一下,假若有5个数据,他的范围在1到10之间

这个时候我们不难发现,如果创建一个带有1到10下标的表格把对应数据存放起来,这样一来我们将来要找目标数据的时候就会很方便,只需要对应下标就可以找到。

但是如果保持5个数据数量不变的情况下,但是把范围改变至1到20,并且这个时候我们不能把表进行扩容

假若这个时候指定规则,不管目标数据为个位数还是十位数,数据的个位数是什么我们就放到什么位置,这个时候我们把12放到2下标的位置,表的内容就产生了冲突

哈希函数

针对冲突的发生,又引出了一个概念,哈希函数可以有很多种,举个常见的,线性函数,比如: Hash(Key)= A*Key + B ,但是大多数情况下,我们使用除留余数法,函数为: Hash(Key)= Key % 表长

负载因子

散列表的负载因子=元素个数/表长,通常来说负载因子越小,冲突的概率越低,就像马路一样,如果马路越宽,那么堵车的可能性也会变低。我们通常扩展列表的长度来使得负载因子扩展,通常情况下,如果负载因子达到了0.75,我们就要对散列表进行扩容

解决冲突

既然有了哈希函数,并且产生了冲突,那我们就要解决冲突,解决冲突可以分为两种方式,开散列和闭散列。

首先来说说闭散列,闭散列我们通常采用线性探测的方式来解决冲突,按上面的例子来简单说就是,12得到的结果是2要放在2的位置,但是2已经有了数据,这个时候我们就把12放到下一个为空的位置

这种解决方式虽然简单粗暴,但是必然会影响查找的时间复杂度,以为增加后续可能发生的冲突

所以我们就要用开散列的方法

哈希桶

哈希表的底层就是哈希桶,哈希桶是什么呢?哈希桶就是在前面我们画的表格的基础上,每一个表格都宽展出一个链表,使得一个下标能存放更多的数据

java 复制代码
public class HashBucket {//哈系桶是哈希查找的关键

    static class Node{
        public int key;
        public int value;
        public Node next;
        public Node(int key,int value){
            this.key = key;
            this.value = value;
        }
    }

    public Node[] array;

    public int usedSize;

    public HashBucket() {
        array = new Node[10];
    }

    public void putLast (int key,int value){//尾插法,这里的尾差指的是,在表对应的位置上,对链表结构进行尾插
        int index = key % array.length;
        Node node = new Node(key,value);
        if(array[index] == null){//第一次插入
            array[index] = node;
            usedSize++;
            if(loadFactor() >=0.75){
                resize();
            }
            return;
        }
        Node cur = array[index];//若不是第一次插入,则进行尾插
        while(cur.next!=null){//尾插
            cur = cur.next;
        }

        cur.next = node;
        usedSize++;

        if(loadFactor() >= 0.75){
            resize();
        }
    }

    public void putFirst (int key , int value) {
        int index = key % array.length;
        Node node = new Node(key,value);
        Node cur = array[index];
        //先遍历一遍链表结构,检查是否存在当前的key
        while(cur!=null){
            if(cur.key==key){
                cur.value = value;//若存在当前key,则把当前的value更新掉
                return;
            }
        }
        node.next = array[index];
        array[index] = node;
        usedSize++;

        if(loadFactor() >= 0.75){
            resize();
        }
    }

    private double loadFactor() {//算负载因子
        return usedSize*1.0/array.length;
    }

    private void resize(){//如果负载因子大于0.75就对表进行扩容
        Node[] tempArray = new Node[array.length*2];

        for (int i = 0; i < array.length; i++) {//遍历原来的表
            Node cur = array[i];
            while(cur != null) {//找到非空的表位
                Node curNext = cur.next;//先记录下表位的next,后续会对next进行修改,所以要进行next的备份
                int newIndex = cur.key % tempArray.length;//算出表位链表上的key对应新表的位置,如13%20=13,就找到13位置
                cur.next = tempArray[newIndex];//头插到新表里,这样就把key:13从链表中分离出来了
                tempArray[newIndex] = cur;
                cur = curNext;//遍历表位的链表
            }
        }

        array=tempArray;//覆盖旧表
    }

    public int get(int key) {
        int index = key % array.length;
        Node cur = array[index];
        while(cur!=null){
            if(cur.key == key){
                return cur.value;
            }
            cur = cur.next;
        }
        return -1;
    }
}

HashMap

java 复制代码
public class TestHashMap {
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        map.put("你好",1);
        map.put("再见",2);
        map.put("嗨",3);
        System.out.println(map.containsKey("你好"));
        System.out.println(map.containsValue(3));
        System.out.println(map.get("你好"));
        System.out.println(map);
    }
}

和TreeMap的方法种类没有区别,但是底层结构上有所区别,HashMap的底层是哈希桶(HashBucket),TreeMap的底层是红黑树。从先阶段的简单层面来说,可以把HashMap理解成一个两行的列表

Key和Value的类型可以自己定义,可以是<String,Integer>,也可以是<String,String>,这一点在某些算法中有所体现。

HashSet

java 复制代码
public class TestHashSet {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        set.add(1);
        set.add(2);
        set.add(3);
        set.add(4);
        System.out.println(set.contains(2));
        set.remove(2);
        System.out.println(set);
    }
}

相比于TreeSet,HashSet的效率更高,因为HashSet不需要进行额外的数据对比操作,在其他方面基本于TreeSet无异。具体要TreeSet还是HashSet需要结合实际来考虑。

相关推荐
快乐肚皮2 分钟前
IntelliJ IDEA Debug 模式功能指南
java·ide·intellij-idea·debug
tianchang6 分钟前
JS 排序神器 sort 的正确打开方式
前端·javascript·算法
_風箏19 分钟前
SpringBoot【ElasticSearch集成 02】Java HTTP Rest client for ElasticSearch Jest 客户端集成
java·后端·elasticsearch
野犬寒鸦33 分钟前
力扣hot100:字母异位词分组和最长连续序列(49,128)
java·数据结构·后端·算法·哈希算法
浮游本尊34 分钟前
Java学习第14天 - 微服务架构与Spring Cloud
java
燃尽余火40 分钟前
Knife4j 文档展示异常的小坑
java·开发语言·spring
aini_lovee44 分钟前
基于MATLAB的雷达系统设计中的信号处理程序
算法·3d
渣哥1 小时前
如果没有双亲委派,Java 会乱成什么样?
java
jokr_1 小时前
C++ STL 顶层设计与安全:迭代器、失效与线程安全
java·c++·安全
Code_Artist1 小时前
[Java并发编程]6.并发集合类:ConcurrentHashMap、CopyOnWriteArrayList
java·后端·源码阅读