Map and Set

news2024/11/28 0:43:55

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);
            }
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[git] cherry pick 将某个分支的某次提交应用到当前分支

功能&#xff1a;将某个分支的某次提交应用到当前分支 应用场景&#xff1a; 在合并分支时&#xff0c;是将源分支的所有内容都合并到目标分支上&#xff0c;有的时候我们可能只需要合并源分支的某次或某几次的提交&#xff0c;这个时候我们就需要使用到git的cherry-pick操作…

超级APP,All in one APP

在信息化时代&#xff0c;企业需要处理的数据和使用的各种系统繁多复杂。然而&#xff0c;传统的应用往往孤立存在&#xff0c;导致数据无法流动和系统无法高效对接。WorkPlus作为一款超级APP&#xff0c;以其全面的功能和强大的集成能力&#xff0c;实现了数据到系统的全方位集…

GitHub Copilot Chat将于12月全面推出;DeepLearning.AI免费新课

&#x1f989; AI新闻 &#x1f680; GitHub Copilot Chat将于12月全面推出&#xff0c;提升开发者的生产力 摘要&#xff1a;GitHub宣布将于12月全面推出GitHub Copilot Chat&#xff0c;这是GitHub Copilot的一个新功能&#xff0c;旨在帮助开发者编写代码。它能够集成到开…

乐优商城(三)品牌管理

1. 品牌的新增 1.1 url 异步请求 点击品牌管理下的新增品牌&#xff0c;填写品牌信息后提交 2.打开浏览器控制台 由此可以得知&#xff1a; 请求方式&#xff1a;POST请求路径&#xff1a;/item/brand请求参数&#xff1a;{name: “测试品牌”, image: “”, cids: “76,32…

【Cocos新手进阶】父级预制体中的数据列表,在子预制体中的控制方法!

本篇文章主要讲解&#xff0c;cocos中在预制体操作过程中&#xff0c;父级预制体生成的数据列表中&#xff0c;绑定了子预制体中的事件&#xff0c;在子预制体的时间中如何控制上级列表的具体操作教程。 日期&#xff1a;2023年11月10日 作者&#xff1a;任聪聪 一、实际效果情…

矩阵起源加入 OpenCloudOS 操作系统开源社区,完成技术兼容互认证

近日&#xff0c;超融合异构云原生数据库 MatrixOne企业版软件 V1.0 完成了与 OpenCloudOS 的相互兼容认证&#xff0c;测试期间&#xff0c;整体运行稳定&#xff0c;在功能、性能及兼容性方面表现良好。 一、产品简介 矩阵起源 MatrixOrigin 致力于建设开放的技术开源社区和…

牛客网:NC69 链表中倒数最后k个结点

一、题目 函数原型&#xff1a; struct ListNode* FindKthToTail(struct ListNode* pHead, int k ) 二、思路 本题需要找到链表中的倒数第k个结点&#xff0c;有两种方法&#xff1a; 1.暴力解法&#xff1a;先遍历一次链表&#xff0c;计算出链表的长度&#xff1b;再遍历一次…

大厂面试题-MySQL索引的优点和缺点?

(图片)索引&#xff0c;是一种能够帮助MySQL高效从磁盘上检索数据的一种数据结构。在MySQL中的InnoDB引擎中&#xff0c;采用了B树的结构来实现索引和数据的存储 MySQL里面的索引的优点有很多&#xff1a; 1. 通过B树的结构来存储数据&#xff0c;可以大大减少数据检索时的磁…

【docker:容器提交成镜像】

容器创建部分请看&#xff1a;点击此处查看我的另一篇文章 容器提交为镜像 docker commit -a "sinwa lee" -m "首页变化" mynginx lxhnginx:1.0docker run -d -p 88:80 --name lxhnginx lxhnginx:1.0为啥没有变啊&#xff0c;首页&#xff1f; 镜像打包 …

CAN总线记录诊断助手 CAN记录仪

随着CAN总线的应用市场越来越多&#xff0c;不仅局限于汽车行业&#xff0c;工程车、特种车、消防、医疗等多行业都是以CAN总线通讯为主。总线的调试诊断也成为技术日常工作&#xff0c;有个好的工具能有效帮助发现问题、解决问题。 来可电子的CANLog-VCI是一款即插即用的CAN数…

从零开始:集成视频直播美颜SDK的步骤及注意事项

如果你是一位开发者&#xff0c;想要为你的视频直播应用添加美颜功能&#xff0c;那么你来对地方了。本文将从零开始&#xff0c;介绍集成视频直播美颜SDK的步骤及需要注意的事项&#xff0c;帮助你顺利实现这一技术目标。 步骤一&#xff1a;选择合适的美颜SDK 在开始之前&…

带有密码的Excel只读模式,如何取消?

Excel文件打开之后发现是只读模式&#xff0c;想要退出只读模式&#xff0c;但是只读模式是带有密码的&#xff0c;该如何取消带有密码的excel只读文件呢&#xff1f; 带有密码的只读模式&#xff0c;是设置了excel文件的修改权限&#xff0c;取消修改权限&#xff0c;我们需要…

银河麒麟操作系统安装_V4/V10系统详细使用教程

银河麒麟桌面操作系统V10是一款简单易用、稳定高效、安全创新的新一代图形化桌面操作系统产品。现已适配国产主流软硬件产品&#xff0c;同源支持飞腾、鲲鹏、海思麒麟、龙芯、申威、海光、兆芯等国产CPU和Intel、AMD平台&#xff0c;通过功耗管理、内核锁及页拷贝、网络、VFS、…

Python使用腾讯云SDK实现对象存储(上传文件、创建桶)

文章目录 1. 开通服务2. 创建存储桶3. 手动上传文件并查看4. python上传文件4.1 找到sdk文档4.2 初始化代码4.3 region获取4.4 secret_id和secret_key获取4.5 上传对象代码4.6 python实现上传文件 5 python创建桶 首先来到腾讯云官网 https://cloud.tencent.com/1. 开通服务 来…

Python 实践

文章目录 一、HttpRequests 一、Http Requests python——Request模块

家庭安全计划 挑战赛| 溺水预防

溺水预防 从了解到行动 家庭安全计划 | 少年急救官 地震避险逃生该怎么做&#xff1f; 起火了该如何应对&#xff1f; 哪些行为容易导致溺水&#xff1f; 家庭风险隐患有哪些&#xff1f; 家庭逃生演练四步骤你会吗&#xff1f; 国际救助儿童会&#xff08;英国&#xff…

C/C++ 动态内存管理(内存是如何分布的?malloc/new,free/delete的用法是什么?区别是什么?)

目录 一、前言 二、C/C中的内存分布 &#x1f4a6;了解内存区域的划分 &#x1f4a6;内存存储区域的对比和注意点 &#x1f4a6;内存管理的常考面试题 三、C语言的动态管理方式 四、C的动态管理方式 &#x1f4a6;new / delete 操作内置类型&#xff08;int,char.....&…

5 Paimon数据湖之表数据查询详解

更多Paimon数据湖内容请关注&#xff1a;https://edu.51cto.com/course/35051.html 虽然前面我们已经讲过如何查询Paimon表中的数据了&#xff0c;但是有一些细节的东西还需要详细分析一下。 首先是针对Paimon中系统表的查询&#xff0c;例如snapshots\schemas\options等等这些…

【每日一题】—— C. Anonymous Informant(Codeforces Round 908 (Div. 2))(思维题)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

(动手学习深度学习)第7章 残差网络---ResNet

目录 ResNet总结 ResNet代码实现ResNet的梯度计算 ResNet 总结 残差块使得很深的网络更加容易训练 甚至可以训练一千层的网络 残差网络对随后的深层神经网络设计产生了深远影响&#xff0c;无论是卷积类网络还是全连接类网络。 ResNet代码实现 导入相关库 import torch fro…