Map and Set

map and set

文章目录

  • map and set
    • 前言
    • 搜索树
      • <1> 操作-查找
      • <2> 操作-插入
      • <3> 操作-删除
      • <4> 代码展示
      • <5> 性能分析
    • Map 和 Set 概念及应用场景
    • Map 和 Set 模型分析
    • Map 的使用
      • <1> Map常用方法说明
      • <3> TreeMap 演示
      • <2> Entry 内部接口
      • <3> Map 中注意事项
      • <4> 遍历 Map 的四种方法
    • Set 的使用
      • <1> Set 常用方法说明
      • <2> TreeSet 演示
      • <3> Set 中注意事项
    • 哈希表
      • <1> 概念
      • <2> 哈希函数
      • <3> 哈希冲突-避免
      • <4> 负载因子
      • <5> 解决哈希冲突-开散列/哈希桶
    • 哈希桶的实现
    • OJ练习

前言

在讲 Map 和 set之前。我们要对其有个简单的了解。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于 set 和 map 接口来说,二者分别包含了 TreeSet、HashSet 和 TreeMap、HashMap 工具类。

TreeMap 和 TreeSet 的底层是一颗二叉搜索树,这颗搜索树是一颗红黑树。对于二叉搜索树来说,每次插入节点,都需要有一个大小的比较,因此 set 和 map 分别实现了 SortedSet 和 SortedMap 这两个接口。

HashMap 和 HashSet 的底层是一个哈希桶

本篇重点讲解:

  1. 二叉搜索树
  2. 哈希桶
  3. Map 和 Set 方法使用及说明

搜索树

性质:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右字数也分别为二叉搜索树

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来简单来实现搜索树的几个方法:1.查找 2.插入 3.删除


<1> 操作-查找

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假设现在要查找一个数 val= 0

查找方法:

  • 如果根节点 cur == val 则找到了,返回 true.
  • 如果根节点 cur > val ,在其左子树继续查找。
  • 若果根节点 cur < val ,在其右子树继续查找。

实现代码如下:

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

<2> 操作-插入

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 当树为空时:要插入的节点就是根节点,即 root = node
  2. 当树不为空时:就相当于找空位置坐下。
    • 如果根节点 cur == val 则说明相同的节点已存在,插入失败,返回 false.
    • 如果根节点 cur > val ,在其左子树继续找位置坐。
    • 若果根节点 cur < val ,在其右子树继续找位置坐。
  3. 当我们找位置坐的时候,一旦找到 ( cur == null ),就无法确定 cur 的父亲节点是啥了。因此要定义一个 parent 节点,来记录 cur 的上一个节点。

实现代码如下:

public boolean insert(int key) {
    TreeNode node = new TreeNode(key);

    TreeNode cur = root;
    TreeNode parent = null;

    if (cur == null) {
        root = node;
        return true;
    }

    while (cur != null) {
        if (cur.val > key) {
            parent = cur;
            cur = cur.left;
        } else if (cur.val < key) {
            parent = cur;
            cur = cur.right;
        } else {
            return false;
        }
    }

    if (parent.val > key) {
        parent.left = node;
    } else {
        parent.right = node;
    }
    UsedSize++;
    return true;
}

<3> 操作-删除

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注:待删除的节点为 cur,待删除的双亲节点为 parent

  1. 当 cur.left == null 时

    • cur 是 root,则 root = cur.right
    • cur 不是 root,cur 是 parent.left, 则 parent.left = cur.right
    • cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
  2. 当 cur.right == null 时

    • cur 是 root,则 root = cur.right
    • cur 不是 root,cur 是 parent.left, 则 parent.left = cur.right
    • cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
  3. 当 cur 的 左子树/右子树 均不为空时

    需要用到替罪羊法进行删除。当左右均不为 null 时,删除一个节点就需要对下面的子树进行调整,因此我们会想到替罪羊方法。

    替罪羊法:找到左树的最小节点,或者右树的最大节点,插入到这个删除的节点中。

代码实现如下:

public boolean removNode(int key) {
    TreeNode cur = root;
    TreeNode parent = null;

    while (cur != null) {
        if (cur.val > key) {
            parent = cur;
            cur = cur.left;
        } else if (cur.val < key) {
            parent = cur;
            cur = cur.right;
        } else {
            Remove(cur, parent);
            return true;
        }
    }
    return false;
}

private void Remove(TreeNode cur, TreeNode parent) {
    if (cur.left == null) {
        if (cur == root) {
            root = cur.right;
        } else if (parent.left == cur) {
            parent.left = cur.right;
        } else {
            parent.right = cur.right;
        }
    } else if (cur.right == null) {
        if (cur == root) {
            root = cur.left;
        } else if (parent.left == cur) {
            parent.left = cur.left;
        } else {
            parent.right = cur.right;
        }
    } else {
        TreeNode targetPartent = cur;
        TreeNode target = cur.right;

        while (target.left != null) {
            targetPartent = target;
            target = target.left;
        }

        cur.val = target.val;

        if (targetPartent.left == target) {
            targetPartent.left = target.right;
        } else {
            targetPartent.right = target.right;
        }
    }
}

<4> 代码展示

以上就是我们简单实现的搜索二叉树,下面展示全部代码(测试代码就不予展示):

public class BinarySearchTree {

    static class TreeNode {
        private int val;
        public TreeNode right;
        public TreeNode left;

        public TreeNode(int val) {
            this.val = val;
        }
    }

    public int UsedSize;

    public TreeNode root;

    public TreeNode search(int val) {

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

        TreeNode cur = root;
        TreeNode parent = null;

        if (cur == null) {
            root = node;
            return true;
        }

        while (cur != null) {
            if (cur.val > key) {
                parent = cur;
                cur = cur.left;
            } else if (cur.val < key) {
                parent = cur;
                cur = cur.right;
            } else {
                return false;
            }
        }

        if (parent.val > key) {
            parent.left = node;
        } else {
            parent.right = node;
        }
        UsedSize++;
        return true;
    }

    public boolean removNode(int key) {
        TreeNode cur = root;
        TreeNode parent = null;

        while (cur != null) {
            if (cur.val > key) {
                parent = cur;
                cur = cur.left;
            } else if (cur.val < key) {
                parent = cur;
                cur = cur.right;
            } else {
                Remove(cur, parent);
                return true;
            }
        }
        return false;
    }

    private void Remove(TreeNode cur, TreeNode parent) {
        if (cur.left == null) {
            if (cur == root) {
                root = cur.right;
            } else if (parent.left == cur) {
                parent.left = cur.right;
            } else {
                parent.right = cur.right;
            }
        } else if (cur.right == null) {
            if (cur == root) {
                root = cur.left;
            } else if (parent.left == cur) {
                parent.left = cur.left;
            } else {
                parent.right = cur.right;
            }
        } else {
            TreeNode targetPartent = cur;
            TreeNode target = cur.right;

            while (target.left != null) {
                targetPartent = target;
                target = target.left;
            }

            cur.val = target.val;

            if (targetPartent.left == target) {
                targetPartent.left = target.right;
            } else {
                targetPartent.right = target.right;
            }
        }
    }
}

<5> 性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个数据集合,如果数据插入的次序不同,可能得到不同结构的二叉搜索树:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

**最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:**log2N

**最差情况下,二叉搜索树退化为单支树,其平均比较次数为:**N/2

Map 和 Set 概念及应用场景

Map 和 set 是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。

以前常见的搜索方式有:

  1. 直接遍历,时间复杂度为O(N),元素比较多效率就会非常慢。
  2. 二分查找,时间复杂度为O(log2N),但搜索前数据必须是有序的。

上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作。而现实中的查找却并非如此,通常会夹杂着插入以及删除操作,即动态查找,本篇介绍的 Map 和 Set 则是一种适合动态查找的容器。

Map 和 Set 模型分析

一般把搜索的数据称为关键字 (Key),和关键字对应的称为值 (Value),将其称之为 Key-value 的键值对,所以模型会有两种:

  1. 纯 Key 模型

    例:快速查找某个 名字(key) 在不在通讯录中。

  2. Key-Value 模型

    例:查找某个 单词(key) 在字典中出现的 次数(value)

Map 中存储的就是 Key-value 的键值, Set 中只存储了 Key.

Map 的使用

说明:Map 是一个接口类,该类没有继承自 Collection,该类中存储的是 <K,V> 结构的键值对,并且 K 一定是唯一的,不能重复。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

map 实现了 SortedMap 这个抽象类

<1> Map常用方法说明

方法解释
V get(Object key)返回 key 对应的 value
V getOrDefault(Object key, V defaultValue)返回 key 对应的 value,key 不存在,返回默认值
V put(K key, V value)设置 key 对应的 value
V remove(Object key)删除 key 对应的映射关系
Set keySet()返回所有 key 的不重复集合
Collection values()返回所有 value 的可重复集合
Set<Map.Entry<K, V>> entrySet()返回所有的 key-value 映射关系
boolean containsKey(Object key)判断是否包含 key
boolean containsValue(Object value)判断是否包含 value

<3> TreeMap 演示

public static void main(String[] args) {
    Map<String, String> map = new TreeMap();

    map.put("Key1", "Value1");
    map.put("Key2", "Value2");
    map.put("Key3", "Value3");
    map.put("Key4", "Value4");
    map.put("Key5", "Value5");

    System.out.println(map.getOrDefault("aaa", "nothing"));  //nothing

    map.put("Key1", "aaa");           //修改 Key1 的值为aaa

    Set<String> set = map.keySet();
    System.out.println(set);          //[Key1, Key2, Key3, Key4, Key5]

    Collection<String> collection = map.values();
    System.out.println(collection);              //[Value1, Value2, Value3, Value4, Value5]
}

<2> Entry 内部接口

Entry 是 java 为 Map 提供的内部接口,其主要提供 getKey()、getValue()、setValue()… 等方法。

Map 中提供的 entrySet 方法,用 Set 来存储,返回值类型是 Map.Entry<K, V>。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

public static void main(String[] args) {
    Map<String, String> map = new TreeMap();

    map.put("Key1", "Value1");
    map.put("Key2", "Value2");
    map.put("Key3", "Value3");
    map.put("Key4", "Value4");
    map.put("Key5", "Value5");

    Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
    for (Map.Entry<String, Integer> entry : entrySet) {
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }
}

打印结果:
key= Key1 and value= Value1
key= Key2 and value= Value2
key= Key3 and value= Value3
key= Key4 and value= Value4
key= Key5 and value= Value5

<3> Map 中注意事项

  1. Map 是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类 TreeMap 或者 HashMap.

  2. Map 中存放键值对的Key是唯一的,value是可以重复的.

  3. 在 TreeMap 中插入键值对时,value 可以为空,key 不能为空,否则就会抛 NullPointerException 异常。但是 HashMap 的 key 和 value 都可以为空。

  4. Map 中的 Key 可以全部分离出来,存储到 Set 中来进行访问(因为Key不能重复)。

  5. Map 中的 value 可以全部分离出来,存储在 Collection 的任何一个子集合中(value可能有重复)。

  6. Map 中键值对的 Key 不能直接修改,value 可以修改,如果要修改 key,只能先将该 key 删除掉,然后再来进行重新插入。

  7. TreeMap 和 HashMap 的区别。

    Map底层结构TreeMapHashMap
    底层结构红黑树Hash桶
    插入/删除/查找时间复杂度O(log2N)O(1)
    是否有序关于Key有序无序
    线程安全不安全不安全
    插入/删除/查找区别需要进行元素比较通过哈希函数计算哈希地址
    比较与覆写key必须能够比较,否则会抛出 ClassCastException 异常自定义类型需要覆写equals和hashCode方法
    应用场景需要Key有序场景下Key是否有序不关心,需要更高的时间性能

<4> 遍历 Map 的四种方法

public static void main(String[] args) {
    Map<String, String> map = new HashMap<String, String>();
    
    map.put("1", "value1");
    map.put("2", "value2");
    map.put("3", "value3");

    //第一种:普遍使用,二次取值,效率较低
    System.out.println("通过Map.keySet遍历key和value:");
    for (String key : map.keySet()) {
        System.out.println("key= "+ key + " and value= " + map.get(key));
    }

    //第二种
    System.out.println("通过Map.entrySet使用iterator遍历key和value:");
    Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<String, String> entry = it.next();
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }

    //第三种:推荐,尤其是容量大时,效率较高
    System.out.println("通过Map.entrySet遍历key和value");
    for (Map.Entry<String, String> entry : map.entrySet()) {
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }

    //第四种
    System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
    for (String v : map.values()) {
        System.out.println("value= " + v);
    }
}

Set 的使用

说明:Set 与 Map 主要的不同有两点:Set 是继承自 Collection 的接口类,Set 中只存储了 Key

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

<1> Set 常用方法说明

方法解释
boolean add(E e)添加元素,但重复元素不会被添加成功
void clear()清空集合
boolean contains(Object o)判断 o 是否在集合中
Iterator iterator()返回迭代器
boolean remove(Object o)删除集合中的 o
int size()返回set中元素的个数
boolean isEmpty()检测set是否为空,空返回true,否则返回false
Object[] toArray()将set中的元素转换为数组返回
boolean containsAll(Collection<?> c)集合c中的元素是否在set中全部存在,是返回true,否则返回false
boolean addAll(Collection<? extends E> c)将集合c中的元素添加到set中,可以达到去重的效果

<2> TreeSet 演示

public static void main(String[] args) {
    Set<String> set = new TreeSet<>();

    set.add("hello");
    set.add("world");
    set.add("abcd");

    Iterator<String> it = set.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());         
    }

    System.out.println(set.contains("hello"));  // true

    System.out.println(set.remove("abcd"));     //true

    System.out.println(set.size());             // 2

    System.out.println(set.containsAll(set));   //true
}

<3> Set 中注意事项

  1. Set 是继承自 Collection 的一个接口类。

  2. Set 中只存储了 key,并且要求 key 一定要唯一。

  3. TreeSet 的底层是使用 Map 来实现的,其使用 key 与 Object 的一个默认对象作为键值对插入到 Map 中的。

  4. Set 最大的功能就是对集合中的元素进行去重。

  5. 实现 Set 接口的常用类有 TreeSet 和 HashSet,还有一个 LinkedHashSet,LinkedHashSet 是在 HashSet 的基础上维护了一个双向链表来记录元素的插入次序。

  6. Set 中的 Key 不能修改,如果要修改,先将原来的删除掉,然后再重新插入。

  7. TreeSet 中不能插入 null 的 key,HashSet 可以。

  8. TreeSet 和 HashSet 的区别。

    Set 底层结构TreeSetHashSet
    底层结构红黑树哈希桶
    插入/删除/查找时间复杂度O(log2N)O(1)
    是否有序关于 key 有序不一定有序
    线程安全不安全不安全
    插入/删除/查找区别按照红黑树的特性来进行插入和删除先计算 key 哈希地址然后进行插入和删除
    比较与覆写key 必须能够比较,否则会抛出 ClassCastException异常自定义类型需要覆写 equals 和 hashCode方法
    应用场景需要 Key 有序场景下Key 是否有序不关心,需要更高的时间性能

哈希表

<1> 概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log2N),搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素

该理想结构中:

  • 插入元素

    根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

  • 搜索元素

    对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

该方式即为哈希方法哈希方法中使用的转换函数称为哈希函数,构造出来的结构称为哈希表(HashTable)(或者称散列表)


<2> 哈希函数

例如:数据集合{1,7,6,4,5,9};此数据该如何存储??

根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。计算出来的哈希值就是要存储的数组下标。

哈希函数设置为:hash = key % capacity(capacity为存储元素底层空间总的大小)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用该方法进行搜索不必进行多次关键码的比较,因此 搜索/插入 的速度比较快

<3> 哈希冲突-避免

问题:按照上述哈希方式,向集合中插入元素 44,会出现什么问题?

会插入到下标为 4 的数组,有可能会覆盖之前插入的数据 14。不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一个问题,冲突的发生是必然的,但我们能做的应该是尽量的降低冲突率

引起哈希冲突的一个原因可能是:哈希函数设计不够合理

哈希函数设计原则

  1. 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  2. 哈希函数计算出来的地址能均匀分布在整个空间中
  3. 哈希函数应该比较简单

常见哈希函数

  1. 直接定制法

    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况。

    使用场景:适合查找比较小且连续的情况

  2. 除留余数法

    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于 m 的质数 p 作为除数,按照哈希函数:Hash(key) = key % p(p<=m),将关键码转换成哈希地址。

<4> 负载因子

散列表的载荷因子定义为: Q = 填入表中的元素个数 / 散列表的长度

负载因子和冲突率的粗略图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在哈希表中,负载因子是一个重要的参数,它决定了哈希表的性能和空间利用率。负载因子太低会导致大量的空桶浪费空间,负载因子太高会导致大量的碰撞,降低性能。通常情况下,0.75 的负载因子在空间利用率和性能之间取得了良好的平衡。

所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率。**已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。**在Java中的HashMap中,负载因子是一个重要的参数,当HashMap中的元素个数超过了容量乘以负载因子时,就会进行扩容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

<5> 解决哈希冲突-开散列/哈希桶

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

开散列,可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了。

上面提到了,哈希桶其实可以看作将大集合的搜索问题转化为小集合的搜索问题了,那如果冲突严重,就意味着小集合的搜索性能其实也时不佳的,这个时候我们就可以将这个所谓的小集合搜索问题继续进行转化,例如:

  1. 每个桶的背后是另一个哈希表
  2. 每个桶的背后是一棵搜索树

哈希桶的实现

实现的此哈希桶,其负载因子为 0.75,哈希函数为 hash = key % capacity .

原理:数组里面套链表。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码实现如下:

public class HashBucket {

    static class Node {
        private int key;
        private int value;
        private Node next;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    public Node[] array;
    public int usedSize;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;   //默认负载因子大小 0.75
    
    public HashBucket () {
        this.array = new Node[10];   //构造方法中初始化容量capacity为10
    }

    public void put (int key, int val) {
        Node node = new Node(key, val);
        int index = key % array.length;

        Node cur = array[index];

        while (cur != null) {
            if (cur.key == key) {
                cur.value = val;
                return;
            }
            cur = cur.next;
        }
        node.next = array[index];
        array[index] = node;

        usedSize ++;

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

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

    private void resize () {             //负载因子过大,进行扩容
        Node[] tmpArray = new Node[array.length * 2];
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null) {
                Node curNext = cur.next;
                int index = cur.key % tmpArray.length;

                cur.next = tmpArray[index];
                tmpArray[index] = cur;

                cur = curNext;
            }
        }
        array = tmpArray;
    }

    public int getValue(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;
    }
}

结合泛型代码如下:

注意:

因为 key 不是具体的数值,而是一个对象,因此要用到 hashcode() 来计算该对象的哈希值码。

hashCode 是jdk 根据对象的地址或者字符串或者数字计算该对象的哈希码值的方法。

下列代码中用到 equals() 方法,因此需要重写 equals(),我们需要注意一个点:在重写 equals() 方法后必须对 hashcode() 方法进行重写。

public class newHashBucket<K, V>{
    static class Node<K, V> {
        private K key;
        private V value;
        private Node<K, V> next;
        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }
    
    public Node<K, V>[] array = (Node<K, V>[])new Node[10];
    
    public int usedSize;
    
    public static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    public newHashBucket() {
    }
    
    public void put(K key, V val) {
        Node<K, V> node = new Node<>(key, val);
        int hash = key.hashCode();
        int index = hash % array.length;

        Node<K,V> cur = array[index];

        while(cur != null) {
            if (cur.key.equals(key)) {
                cur.value = val;
                return;
            }
            cur = cur.next;
        }
        node.next = array[index];
        array[index] = node;

        usedSize ++;

        if(loadFactor() >= DEFAULT_LOAD_FACTOR) {
            resize();
        }
    }
    
    private float loadFactor() {
        return usedSize*1.0f / array.length;
    }
    
    private void resize () {
        Node[] tmpArray = new Node[array.length * 2];
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null) {
                Node curNext = cur.next;
                int hash = cur.key.hashCode();
                int index = hash % tmpArray.length;
                
                cur.next = tmpArray[index];
                tmpArray[index] = cur;
                
                cur = curNext;
            }
        }
        array = tmpArray;
    }
    
    public V getValue(K key) {
        int hash = key.hashCode();
        int index = hash % array.length;
        Node<K, V> cur = array[index];
        while (cur != null) {
            if (cur.key.equals(key)) {
                return cur.value;
            }
            cur = cur.next;
        }
        return null;
    }
}


//测试代码
class Person {
    public String id;

    public Person(String id) {
        this.id = id;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

public class Testnew {
    public static void main(String[] args) {
        Person person1 = new Person("1");
        Person person2 = new Person("22");
        Person person3 = new Person("3");
        Person person4 = new Person("4");
        Person person5 = new Person("56");
        Person person6 = new Person("6");
        Person person7 = new Person("7");
        Person person8 = new Person("999");
        
        newHashBucket<Person, String> newHashBucket = new newHashBucket<>();
        
        newHashBucket.put(person1, "value1");
        newHashBucket.put(person2, "value2");
        newHashBucket.put(person3, "value3");
        newHashBucket.put(person4, "value4");
        newHashBucket.put(person5, "value5");
        newHashBucket.put(person6, "value6");
        newHashBucket.put(person7, "value7");
        newHashBucket.put(person8, "value8");
        
        System.out.println(newHashBucket.getValue(person1));	//value1
        System.out.println(newHashBucket.getValue(person2));	//value2
        System.out.println(newHashBucket.getValue(person3));	//value3
        System.out.println(newHashBucket.getValue(person4));	//value4
        System.out.println(newHashBucket.getValue(person5));	//value5
        System.out.println(newHashBucket.getValue(person6));	//value6
        System.out.println(newHashBucket.getValue(person7));	//value7
        System.out.println(newHashBucket.getValue(person8));	//value8
    }
}

OJ练习

138. 随机链表的复制

class Solution {
    public Node copyRandomList(Node head) {
        Map<Node, Node> map = new HashMap<>();
        Node cur = head;

        while (cur != null) {
            Node node = new Node(cur.val);
            map.put(cur,node);
            cur = cur.next;
        } 

        cur = head;
        while (cur != null) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }

        return map.get(head);
    }
}

692. 前K个高频单词

import java.util.*;

class Solution {

    public static List<String> topKFrequent(String[] words, int k) {

        //1、计算每个单词出现的次数
        Map<String, Integer>  map = new HashMap<>();
        for (String s : words) {
            if(map.get(s) == null) {
                map.put(s, 1);
            } else {
                int val = map.get(s);
                map.put(s,val + 1);
            }
        }

        //2、已经统计完毕,创建小根堆
        PriorityQueue<Map.Entry<String, Integer>> minHeap = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                if(o1.getValue().compareTo(o2.getValue()) == 0) {
                    return o2.getKey().compareTo(o1.getKey());
                }
                return o1.getValue().compareTo(o2.getValue());
            }
        });

        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            if(minHeap.size() < k) {
                minHeap.offer(entry);
            } else {
                Map.Entry<String, Integer> top = minHeap.peek();
                if(top.getValue().compareTo(entry.getValue()) == 0) {
                    if (top.getKey().compareTo(entry.getKey()) > 0) {
                        minHeap.poll();
                        minHeap.offer(entry);
                    }
                } else {
                    if(top.getValue().compareTo(entry.getValue()) < 0) {
                        minHeap.poll();
                        minHeap.offer(entry);
                    }
                }
            }
        }

        List<String> ret = new ArrayList<>();
        for (int i = 0; i < k; i++) {
            String s = minHeap.poll().getKey();
            ret.add(s);
        }

        Collections.reverse(ret);
        return ret;
    }
}

旧键盘

import java.util.HashSet;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()) {
            String str1 = scanner.nextLine();
            String str2 = scanner.nextLine();
            func(str1,str2);
        }

    }

    /**
     *
     * @param str1 : 期待输入的
     * @param str2 : 实际输入的
     */
    public static void func (String str1, String str2) {
        Set<Character> set = new HashSet<>();   // f n g 4 n

        for (char ch : str2.toUpperCase().toCharArray()) {
            set.add(ch);
        }

        Set<Character> setBroken = new HashSet<>();
        for (char ch : str1.toUpperCase().toCharArray()) {
            if(!set.contains(ch) && !setBroken.contains(ch)) {
                setBroken.add(ch);
                System.out.print(ch);
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/128408.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android逆向fiddler抓包工具——理解HTTP协议

HTTP协议格式 HTTP协议是一种应用非常广泛的应用层协议&#xff0c;当我们在浏览器中输入一个URL(“网址”)时&#xff0c;浏览器就会给客户端发送一个HTTP请求&#xff0c;服务器收到请求之后&#xff0c;就会返回一个HTTP响应。 为了能够看到HTTP请求和响应的详细内容&…

最新整理【剑侠情缘龙雀修复BGU版】linux服务端带授权后台+详细教程+包进游戏

搭建资源下载地址&#xff1a;最新整理【剑侠情缘龙雀修复BGU版】linux服务端带授权后台详细教程包进游戏 - 海盗空间

卷积神经网络中参数量的计算原理及方法

手动计算参数量: 1. 卷积层参数计算方法: 参数量计算公式 卷积核宽度 * 卷积核高度 * 输入层通道数 * 输出层通道数 bias(输出层通道数) 注意:池化层没有参数(只是在已知数据区域里求个最大值)输入层通道数就是上层的卷积核数量 输出层通道数等于卷积核个数:输入层通道数经过…

滤波器实现

滤波器实现 卷积和滤波 滤波的数学基础是卷积。对于有限冲激响应 (FIR) 滤波器&#xff0c;滤波运算的输出 y(k) 是输入信号 x(k) 与冲激响应 h(k) 的卷积&#xff1a; y(k)∞∑l−∞h(l) x(k−l). 如果输入信号也是有限长度的&#xff0c;您可以使用 MATLAB conv 函数来执行…

原神私服搭建服务器配置该如何选择

原神是一款开放世界的冒险游戏&#xff0c;自从这款游戏上线以来&#xff0c;就受到越来越多的玩家喜欢&#xff0c;因为这款游戏的设定比较少见&#xff0c;剧情也非常精彩&#xff0c;有一些玩家为了更好的游戏体验想要搭建原神的私服&#xff0c;满足玩家的需求&#xff0c;…

SpringBoot 使用EasyExcel 导出Excel报表(单元格合并)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、导入依赖二、代码1. 导出简单的Excel2. 代码控制导出报表的格式 总结 前言 SpringBoot 使用Alibaba提供的EasyExcel导出Excel报表。 本文中涉及的业务逻辑…

SpringDataJpa(一)

一、JPA概述 1.1 ORM概述 ORM&#xff08;Object-Relational Mapping&#xff09; 表示对象关系映射。在面向对象的软件开发中&#xff0c;通过ORM&#xff0c;就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联&#xff0c;操作对象就可以直…

K9203 996920302 面向DNP3的网络安全解决方案

K9203 996920302 面向DNP3的网络安全解决方案 2014年ISA卓越技术创新奖获得者&#xff0c;超电子&#xff0c;3eTI的CyberFence工业防火墙解决方案提供强大加密和应用程序级深度数据包检测(DPI)功能。最近&#xff0c;3eTI为其CyberFence产品线增加了DNP3(分布式网络协议)支持…

快速开发一个简单实用的MES系统?

题主在一个光伏组件工厂做生产管理&#xff0c;但工厂竟然没有MES系统&#xff0c;于是想自己开发一个简单的MES系统。那么我们来看看题主对于开发MES系统的要求—— 对系统&#xff1a;每一个产品都有一个条形码&#xff0c;希望系统可以追踪生产计划下的产品的生产状态&…

java项目之戒烟网站(ssm+vue)

项目简介 戒烟网站实现了以下功能&#xff1a; 用户可以对首页&#xff0c;用户分享&#xff0c;论坛交流&#xff0c;公告文章&#xff0c;个人中心&#xff0c;后台管理等功能进行操作。 管理员可以对网站所有功能进行管理&#xff0c;包括管理用户的基本信息。 &#x1f4…

2011年09月06日 Go生态洞察:Go语言的反射法则

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

chatglm3-6b部署及微调

chatglm3-6b部署及微调 modelscope: https://modelscope.cn/models/ZhipuAI/chatglm3-6b/filesgithub: https://github.com/THUDM/ChatGLM3镜像: ubuntu20.04-cuda11.7.1-py38-torch2.0.1-tf1.15.5-1.8.1v100 16G现存 单卡 安装 软件依赖 # 非必要无需执行 # pip install -…

OC-编译错误

明明包含了头文件&#xff0c;但是还是显示未知的类型 可能这个头文件被某个宏包住了 #if defined(__cplusplus) 在 C 代码中包含了一个 C 的头文件会显示这个错误“the util lib only be used in c”&#xff0c;此时用 #if defined(__cplusplus) #endif 包一下就行了&…

NSS [HUBUCTF 2022 新生赛]checkin

NSS [HUBUCTF 2022 新生赛]checkin 判断条件是if ($data_unserialize[username]$username&&$data_unserialize[password]$password)&#xff0c;满足则给我们flag。正常思路来说&#xff0c;我们要使序列化传入的username和password等于代码中的两个同名变量&#xff0…

数字滤波器分析---频率响应

数字滤波器分析---频率响应 幅值、相位、冲激和阶跃响应、相位和群延迟、零极点分析。 分析滤波器的频域和时域响应。可视化复平面中的滤波器极点和零点。 频率响应 数字域 freqz 使用基于 FFT 的算法来计算数字滤波器的 Z 变换频率响应。具体来说&#xff0c;语句 [h,w]…

多组学整合,快速定位关键代谢通路,解析分子机制

生物学是一种复杂的学科&#xff0c;往往单一组学无法探究想要了解的生物学问题&#xff0c;这时就要运用到多组学联合分析。近年来&#xff0c;多组学研究的不断发展和持续火热&#xff0c;越来越多的研究者开始将微生物组学和代谢组学联合起来。16s全长扩增子测序可提供细菌构…

【微信公众号开发】1.1 微信公众号开发课程内容介绍

一、微信公众号介绍 1、公众号类型及基本介绍 服务号、订阅号、小程序之间的关联及区别 2、编辑模式的使用 非开发者使用微信公众号的方式&#xff0c;通过微信公众号提供的平台来编辑 3、开发模式及预备知识介绍 如果我们不想使用默认的编辑模式&#xff0c;可以在具备一…

【算法练习Day44】最长递增子序列最长连续递增序列最长重复子数组

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 最长递增子序列最长连续递增…

利用MSF设置代理

1、介绍&#xff1a; 通过MSF拿到一个机器的权限后&#xff0c;通过MSF搭建socks代理&#xff0c;然后通内网。 拿到目标权限&#xff0c;有很多方法&#xff0c;比如&#xff1a;①ms17-010 ②补丁漏洞 ③MSF生成后门 在此直接使用MSF生成后门 MSF中有三个代理模块&#x…

k8s ingress基础

一、ingress 简介 在k8s集群中&#xff0c;service和pod的ip为内网ip&#xff0c;仅集群内部才可以访问。如果外部应用想要直接访问集群内的服务&#xff0c;就需要把外部请求通过负载均衡转发到service上&#xff0c;然后再由kube-proxy组件将其转发给后端pod。一般service可…