数据结构——散列表

news2024/11/18 7:45:27

参考书籍:

  • 《数据结构与抽象:Java语言描述》 第四版

一、背景知识

  • 散列(hashing):是仅利用项的查找键,无需查找就可确定其下标的一项技术
  • 散列表(hash table):数组
  • 散列索引(hash index):下标
  • 散列函数(hash function):根据查找键得到元素在散列表中的整数下标
    • 查找键映射(map)或散列(hash)到下标
  • 散列函数使用hashCode()方法从查找键来计算散列码(hash code),然后将散列码压缩为散列表的地址。
    • 散列码:c;
    • 散列表的位置个数:n;(大于2的素数)
    • c%n 在0~n-1之间
    • c%n 是有n个位置的散列表的理想下标
  • 对象不equals,散列码就不同。
  • 冲突(collision):多个查找键映射到散列表中的同一个位置
  • 冲突解决方案(collision resolution):
    • 1、开放地址法
      • (1)线性探查(linear probing):检查散列表中的连续位置,从原始散列地址开始,每次的增量为1,直到找到下一个可用的位置
        • 出现的问题:基本聚集(primary clustering),即散列表中一组组(簇,cluster)连续的位置被占用
        • 优点:能够到达散列表的每个位置
      • (2)二次探查(quadratic probing):从最初的散列地址 k 开始,每次的增量为j^{2}(j\geq 0),检查地址为k+j^{2}(j\geq 0)的位置。
        • 如果探查序列到达散列表的表尾,它会绕回到表的开头。越到序列的后面距离增量会越大
        • 出现的问题:通过检查散列表中最初的散列地址加上j^{2}(j\geq 0)的位置,避免了基本聚集,但可能会导致二级聚集(增加了探查序列的长度)
      • (3)双散列(double hashing):检查散列表中最初的散列地址加上由第二个散列函数定义的增量的位置
        • 优点:避免了基本聚集和二级聚集
    • 2、拉链法
      • 桶(bucket):每个位置可以表示多个值,多用链式结构,因为可以给桶按需分配内存
      • 散列表的每个位置都是一个个链式桶,先散列查找键,然后在链式桶里查找键-值对。如果允许重复键,就把新项添加到链头,否则遍历链式桶,添加到链尾。
      • 改变了散列表的结构
  • 散列表:
    • 时间复杂度O(1)

二、(Hash Table) 

老东西,现在不咋用了

三、哈希映射(Hash Map)

//哈希映射常用方法:
Map<T,T> map=new HashMap<>();//创建一个哈希映射
map.clear();//清空哈希映射
map.containsKey(key);//是否包括元素key,返回boolean值
map.containsValue(value);//是否包括元素value,返回boolean值
map.get(key);//返回键key对应的值value
map.isEmpty();//哈希映射是否为空,返回boolean值
map.put(key,value);//把键值对存入哈希映射
map.remove(key);//移除key这对键值对
map.size();//获取哈希映射的大小
map.getOrDefault(key,value);//如果map中包含key,就获取对应的值,否则返回value

哈希集合的遍历方式:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Test {
    public static void main(String[] args) {
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("1","value1");
        hashMap.put("2","value2");
        hashMap.put("3","value3");
        hashMap.put("4","value4");
        hashMap.put("5","value5");
        hashMap.put("6","value6");

        /**
         *  第一种遍历方式,采用for遍历key值,然后通过key去获取hashmap中的数据
         */
        for (String key:hashMap.keySet()) {
            System.out.println("key: " + key + " value: " + hashMap.get(key));
        }

        /**
         * 第二种遍历方式,采用Iterator 把hashmap中的数据放到迭代器中,然后用while循环把迭代器中的数据都读出来
         */
        Iterator iterator = hashMap.entrySet().iterator();
        while(iterator.hasNext()) {
            Map.Entry<String, String> entry=(Map.Entry<String, String>) iterator.next();
            System.out.println("Key: "+entry.getKey()+" Value: "+entry.getValue());
        }

        /**
         * 第三种遍历方式,采用for循环遍历hashmap中的数据,使用方便,但是数据量小时好用,如果数据量大的话非常消耗性能
         */
        for(Map.Entry<String, String> entry: hashMap.entrySet()) {
            System.out.println("Key: "+ entry.getKey()+ " Value: "+entry.getValue());
        }
    }
}

三、  哈希集合(Hash Set)

//哈希集合常用方法:
Set<T> set=new HashSet<>();//创建一个T类型的哈希集合
set.add(e);//添加元素
set.clear();//清空哈希集合
set.contains(e);//哈希集合中是否包含元素e,返回boolean值
set.isEmpty();//哈希集合中是否为空,返回boolean值
set.iterator();//返回一个迭代器
set.remove(e);//删除元素e
set.size();//获取哈希集合的大小
set.toArray();//返回一个包含哈希集合所有元素的数组

HashTableHashMapHashSet
实现了接口Map实现了接口Map实现了接口Collection
存储键值对存储键值对仅存储对象(元素)
线程安全线程不安全线程不安全
不允许键或值为null,且不保证元素顺序允许键和值为null,但不保证元素顺序,且键不可重复允许值为null,但不保证元素顺序,且元素不可重复

四、例题

1、两数之和

方法一:双重for循环

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] arr=new int[2];//把结果存入数组中返回
        for(int i=0;i<nums.length-1;i++){
            for(int j=i+1;j<nums.length;j++){
                if(nums[i]+nums[j]==target){
                    arr[0]=i;
                    arr[1]=j;
                    break;
                }
            }
        }
        return arr;
    }
}

方法二:哈希表

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] arr=new int[2];
        Map<Integer,Integer> ht=new HashMap<>();//创建一个哈希表
        for(int i=0;i<nums.length;i++){
            if(ht.containsKey(target-nums[i])){
                arr[0]=i;
                arr[1]=ht.get(target-nums[i]);//键
                break;
            }
            ht.put(nums[i],i);//哈希表的键:数组元素;值:数组元素下标
        }
        return arr;
    }

}
/**
思路:
1、在哈希表中查找target-x
2、如果哈希表中不存在target-x,再将x插入哈希表中,即可保证x不会和自己匹配
 */

进一步优化:(去掉数组后,降低了时间复杂度)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> ht=new HashMap<>();//创建一个哈希表
        for(int i=0;i<nums.length;i++){
            if(ht.containsKey(target-nums[i])){
                return new int[]{i,ht.get(target-nums[i])};
            }
            ht.put(nums[i],i);//哈希表的键:数组元素;值:数组元素下标
        }
        return new int[0];
    }

}
/**
思路:
1、在哈希表中查找target-x
2、如果哈希表中不存在target-x,再将x插入哈希表中,即可保证x不会和自己匹配
 */

2、字母异位词分组

 方法一:哈希表

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        List<List<String>> list1=new ArrayList<>();
        Map<String,List<String>> hashtable=new HashMap<>();
        //遍历数组,存下标进哈希表
        for(int i=0;i<strs.length;i++){
            //对单词进行重排序
            char[] c=strs[i].toCharArray();
            Arrays.sort(c);
            String s=new String(c);
            //String s=c.toString();
            if(!hashtable.containsKey(s)){//哈希表中未出现这种字母组合
                List<String> list3=new ArrayList<>();
                list3.add(strs[i]);
                hashtable.put(s,list3);
            }else{//哈希表中已有这种字母组合,直接添加单词进去,更新list3
                hashtable.get(s).add(strs[i]);//相当于list3.add(strs[i]);
            }
        }
        //遍历哈希表,存单词进链表
        for(String key:hashtable.keySet()){
            list1.add(hashtable.get(key));
        }
        return list1;
    }
}
/**
思路:
把单词按字母顺序重新排序后,存入哈希表中,当键
遍历哈希表,取出值(一条条链表list3)存入链表list1中返回
 */

简洁化代码:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        for (String str : strs) {
            //将排序之后的字符串作为哈希表的键
            char[] array = str.toCharArray();
            Arrays.sort(array);
            String key = new String(array);
            //如果哈希表中的key存在,就调用map.get(key)方法,返回值 
            //如果不存在,就返回new ArrayList<String>(),创建新链表
            List<String> list = map.getOrDefault(key, new ArrayList<String>());
            list.add(str);
            map.put(key, list);
        }
        //把哈希表里的值取出存入新链表返回
        return new ArrayList<List<String>>(map.values());
    }
}
//哈希表的键为一组字母异位词的标志,哈希表的值为一组字母异位词列表。

3、最长连续序列

 方法一:普通遍历

class Solution {
    public int longestConsecutive(int[] nums) {
        if(nums.length==0){
            return 0;
        }
        Arrays.sort(nums);//对原数组进行排序
        int len=1;//连续序列长度
        int m=1;//最大值 
        if(nums.length>1){
            for(int i=0;i<nums.length-1;i++){
                if(nums[i+1]-nums[i]==1){//元素是连续
                    len++;//长度加一
                    m=Math.max(m,len);//维护一个最大值 
                }else if(nums[i+1]-nums[i]>1){//元素不连续
                    len=1;//连续序列长度重置为0,继续搜寻下一组连续序列
                }    
            }
        }
        return m;
    }
}
/**
思路:
1、对原数组进行排序
2、遍历原数组,维护一个序列长度的最大值 
 */

方法二:哈希表 

考察了hashset的contains()方法查找元素和其元素的无序性

class Solution {
    public int longestConsecutive(int[] nums) {
        Set<Integer> num_set = new HashSet<Integer>();
        for (int num : nums) {//用哈希表存储数组元素,去重(哈希集合不能存储相同的元素)
            num_set.add(num);
        }
        int longestStreak = 0;//最长序列长度
        for (int num : num_set) {//遍历哈希表
            if (!num_set.contains(num - 1)) {//当前值不存在前驱,要么它是连续序列中的第一个元素,要么它不在连续序列,跳过
                int currentNum = num;
                int currentStreak = 1;

                while (num_set.contains(currentNum + 1)) {//当前值存在前驱,进入内层循环,去匹配该组连续序列的数,
                    currentNum += 1;
                    currentStreak += 1;
                }

                longestStreak = Math.max(longestStreak, currentStreak);//维护一个最大值 
            }
        }

        return longestStreak;
    }
}

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

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

相关文章

设计循环队列(c语言)

前言 在上一篇文章中我们了解了关于循环队列的基本特性&#xff1a; 1、当rear front时&#xff0c;表示队列为空 2、当rear 1 front时&#xff0c;表示队列已满 当我们需要实现循环队列时&#xff0c;通常会选择使用链表或数组来存储队列中的元素。而使用数组来实现循环队…

OGG-01224 Address already in use 问题

ERROR OGG-01224 Oracle GoldenGate Manager for Oracle, mgr.prm: Address already in use. ERROR OGG-01668 Oracle GoldenGate Manager for Oracle, mgr.prm: PROCESS ABENDING. 查看端口被占用情况&#xff1a; [rootcenterone ogg]# lsof -i:7809原因mgr 7809 端口被占…

日常办公:批处理编写Word邮件合并获取图片全路径

大家在使用Word邮件合并这个功能&#xff0c;比如制作席卡、贺卡、准考证、员工档案、成绩单、邀请函、名片等等&#xff0c;那就需要对图片路径进行转换处理&#xff0c;此脚本就是直接将图片的路径提取出来&#xff0c;并把内容放到txt格式的文本文档里&#xff0c;打开Excel…

2023年【施工升降机司机(建筑特殊工种)】最新解析及施工升降机司机(建筑特殊工种)考试资料

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 施工升降机司机(建筑特殊工种)最新解析参考答案及施工升降机司机(建筑特殊工种)考试试题解析是安全生产模拟考试一点通题库老师及施工升降机司机(建筑特殊工种)操作证已考过的学员汇总&#xff0c;相对有效帮助施工升…

Halcon Solution Guide I basics(0): 导论解析

文章目录 文章专栏前言文章目录翻译文档的说明 结论LOL比赛结局 文章专栏 Halcon开发 前言 今天开始看Halcon的官方文档。由于市面上的教学主要是以基础的语法&#xff0c;算子简单介绍为主。所以我还是得看官方的文本。别的不多说了。有道词英语词典&#xff0c;启动。 还有…

数字IC基础:有符号数和无符号数的加减运算

相关阅读 数字IC基础https://blog.csdn.net/weixin_45791458/category_12365795.html?spm1001.2014.3001.5482 首先说明&#xff0c;本篇文章并不涉及补码运算正确性的证明&#xff0c;仅是对补码运算在有符号数和无符号数中运行进行讨论。 补码运算最大的作用在于消除计算机…

线程池有几种创建方式?

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

开源的进销存系统都有哪些?

开源的进销存系统有很多&#xff0c;以下是其中一些比较流行的: OpenERP&#xff1a;一个集成了多个业务功能的开源ERP软件&#xff0c;可以实现进销存管理&#xff0c;会计&#xff0c;仓库管理&#xff0c;销售管理等业务功能。 Odoo&#xff1a;是OpenERP的一个分支&#x…

【C语言】数据结构——栈和队列实例探究

&#x1f497;个人主页&#x1f497; ⭐个人专栏——数据结构学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读&#xff1a;一、 栈1. 栈的概念及结构2. 栈的实现3. 实现代码3.1 定义结构体3.2 初始化栈3.3 销毁栈3.4 入栈3.5 出栈…

2023年G3锅炉水处理证考试题库及G3锅炉水处理试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年G3锅炉水处理证考试题库及G3锅炉水处理试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人员上岗证考试大纲随机…

【数据结构】栈和队列的模拟实现

前言&#xff1a;前面我们学习了单链表并且模拟了它的实现&#xff0c;今天我们来进一步学习&#xff0c;来学习栈和队列吧&#xff01;一起加油各位&#xff0c;后面的路只会越来越难走需要我们一步一个脚印&#xff01; &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x…

提升--09-1--AQS底层逻辑实现

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、怎么解释AQS是什么&#xff1f;AQS的本质是JUC包下一个抽象类&#xff0c;AbstractQueuedSynchronizer &#xff08;抽象的队列式同步器&#xff09; 二、AQS核…

基于霍克斯过程的限价订单簿模型下的深度强化学习做市策略

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…

【secureCRT连接Virtual Box里安装的Utuntu】

先说一下为什么要写这篇文章及一些背景问题介绍&#xff0c;楼主第一次使用secureCRT及securtFX这两个软件&#xff0c;在windows系统下访问虚拟机里面的ubuntu系统。看了网上的不少帖子&#xff0c;没有让我清晰明白地知道怎么使用secureCRT。连接不通&#xff0c;不知道是虚拟…

【Python】给出一个包含n个整数的数列,问整数a在数列中的第一次出现是第几个。

问题描述 给出一个包含n个整数的数列&#xff0c;问整数a在数列中的第一次出现是第几个。 输入格式 第一行包含一个整数n。 第二行包含n个非负整数&#xff0c;为给定的数列&#xff0c;数列中的每个数都不大于10000。 第三行包含一个整数a&#xff0c;为待查找的数。 输出格式…

LangChain的函数,工具和代理(一):OpenAI的函数调用

一、什么是函数调用功能 几个月前OpenAI官方发布了其API的函数调用功能(Function calling), 在 API 调用中&#xff0c;您可以描述函数&#xff0c;并让模型智能地选择输出包含调用一个或多个函数的参数的 JSON 对象。API函数“ChatCompletion” 虽然不会实际调用该函数&#…

redis之数据类型

&#xff08;一&#xff09;关系型数据库和非关系型数据库的区别 1、关系型数据库是一个机构化的数据库&#xff0c;列和行 &#xff08;1&#xff09;列&#xff1a;声明对象 &#xff08;2&#xff09;行&#xff1a;记录对象的属性 &#xff08;3&#xff09;表与表之间…

Python (十三) 输出

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

使用pytorch利用神经网络原理进行图片的训练(持续学习中....)

1.做这件事的目的 语言只是工具,使用python训练图片数据,最终会得到.pth的训练文件,java有使用这个文件进行图片识别的工具,顺便整合,我觉得Neo4J正确率太低了,草莓都能识别成为苹果,而且速度慢,不能持续识别视频帧 2.什么是神经网络?(其实就是数学的排列组合最终得到统计结果…

Vue项目 配置项设置

一、项目运行时浏览器自动打开 找到package.json文件 找到"sctipts"配置项 在"serve"配置项最后加上--open "scripts": {"serve": "vue-cli-service serve --open","build": "vue-cli-service build&quo…