首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >数据结构从青铜到王者第十九话---Map和Set(2)

数据结构从青铜到王者第十九话---Map和Set(2)

作者头像
寻星探路
发布2025-12-17 18:36:09
发布2025-12-17 18:36:09
630
举报
文章被收录于专栏:CSDN博客CSDN博客

一、Set 的说明

Set 的官方文档https://docs.oracle.com/javase/8/docs/api/java/util/Set.html Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key。

1、常见方法说明

#注:

(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的区别【HashSet我们在后面最后会讲到】

2、TreeSet的使用案例

代码语言:javascript
复制
import java.util.TreeSet;
import java.util.Iterator;
import java.util.Set;

public static void TestSet(){
    Set<String> s = new TreeSet<>();

    // add(key): 如果key不存在,则插入,返回ture
    // 如果key存在,返回false
    boolean isIn = s.add("apple");
    s.add("orange");
    s.add("peach");
    s.add("banana");
    System.out.println(s.size());
    System.out.println(s);

    isIn = s.add("apple");

    // add(key): key如果是空,抛出空指针异常
    //s.add(null);

    // contains(key): 如果key存在,返回true,否则返回false
    System.out.println(s.contains("apple"));
    System.out.println(s.contains("watermelen"));

    // remove(key): key存在,删除成功返回true
    //              key不存在,删除失败返回false
    //              key为空,抛出空指针异常
    s.remove("apple");
    System.out.println(s);

    s.remove("watermelen");
    System.out.println(s);

    Iterator<String> it = s.iterator();
    while(it.hasNext()){
        System.out.print(it.next() + " ");
    }
    System.out.println();
}

二、哈希表

1、概念

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

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

当向该结构中:

插入元素

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

搜索元素

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

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

例如:数据集合{1,7,6,4,5,9};

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

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

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

2、冲突-概念

对于两个数据元素的关键字和 (i != j),有 != ,但有:Hash( ) == Hash( ),即:不同关键字通过相同哈 希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

3、冲突-避免

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

4、冲突-避免-哈希函数设计

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

哈希函数设计原则

哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1 之间

哈希函数计算出来的地址能均匀分布在整个空间中

哈希函数应该比较简单

4.1常见的哈希函数
4.1.1直接定制法--(常用)

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

优点:简单、均匀

缺点:需要事先知道关键字的分布情况

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

面试题: 387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

我们可以对字符串进行两次遍历。

在第一次遍历时,我们使用哈希映射统计出字符串中每个字符出现的次数。在第二次遍历时,我们只要遍历到了一个只出现一次的字符,那么就返回它的索引,否则在遍历结束后返回 −1。

代码语言:javascript
复制
class Solution {
    public int firstUniqChar(String s) {
        Map<Character, Integer> frequency = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); ++i) {
            char ch = s.charAt(i);
            frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
        }
        for (int i = 0; i < s.length(); ++i) {
            if (frequency.get(s.charAt(i)) == 1) {
                return i;
            }
        }
        return -1;
    }
}
4.1.2除留余数法--(常用)

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

4.1.3平方取中法--(了解)

假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址

平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况

4.1.4折叠法--(了解)

折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和, 并按散列表表长,取后几位作为散列地址。

折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况

4.1.5随机数法--(了解)

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。

通常应用于关键字长度不等时采用此法

4.1.6数学分析法--(了解)

设有n个d位数,每一位可能有 r 种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。

例如:

假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是相同的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现冲突,还可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移位、前两数与后两数叠加(如1234改成12+34=46)等方法。

数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀的情况

#注:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

5、冲突-避免-负载因子调节(重点掌握)

负载因子和冲突率的关系粗略演示

所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率。

已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。

我们会在下一话讲到如何去解决冲突!!!

由于内容较多,会分为多篇讲解,预知后续内容,请看后续博客!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Set 的说明
    • 1、常见方法说明
    • 2、TreeSet的使用案例
  • 二、哈希表
    • 1、概念
    • 2、冲突-概念
    • 3、冲突-避免
    • 4、冲突-避免-哈希函数设计
      • 4.1常见的哈希函数
    • 5、冲突-避免-负载因子调节(重点掌握)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档