(二十三)数据结构-哈希表

news2024/12/23 16:28:34

1 哈希表的基本介绍

1.1 用于存储的数据结构

在计算机中,数组和链表都可以用于数据的存储,既然有数据存储,那么必然要有数据的查询,因此我们在将数据存储进数组和链表中之后,必然要对它们进行查询操作。一个链表的查询时间复杂度为O(n),而一个无序数组的查询时间复杂度也是O(n),对于数组的查询时间,我们尚有讨论的余地,但是链表的查询时间肯定是更长的,因为链表是不连续的空间,它只能一个接一个的遍历查询,不管链表是否有序。但是数组的查询时间,是可以进行改进的,当数组中数据是有序的,我们就可以使用二分查找的方式查找数组中的数据,进而能大量节省查询时间,对于二分查找,并不是一个困难的问题,因为二分法每次都能甩掉一般的数据,因此其时间复杂度肯定比O(n)低很多,它的时间复杂度是O(logn),随着查询次数的增长,其时间复杂度会明显比O(n)低很多。

​ 因此对于数组而言,我们的研究方向便不再是如何找更优的查找方案,而是如何将其更快的排序,因此引出了排序算法,目前主流的排序算法有八种,实际上大家比较认可的排序方法还有更多,值得学习的排序方法至少有十种。现实告诉我们,矛盾很难消除,它只会转移,有了二分查找的数组,查询时间长的矛盾并不能直接消除,因为排序算法也是耗费时间的,查询的时间跑到了排序的时间里去了。对于排序的时间复杂度,最低的为O(nlogn),看上去也不是特别的少,这也就直接导致了两个算法加起来的时间复杂度,比直接无序状态查还耗费时间。

​ 基于这个问题,有人创造性的提出了一种新的思想,那就是:在存储数据的时候,不再来数就存,而是使用一种巧妙的分类方法,将数据们进行分类,进而达到像二分查找一样的大规模缩减查询范围的效果,这就是哈希存储。

1.2 哈希表

哈希表的物理结构多种多样,在基数排序中用到的桶结构,实际上就是一种哈希表,哈希表通常是基于某种分类规则,为存储的数据进行分类,然后将他们存储在不同的索引下,这样我们在查询一个数据的时候,先拿到索引,然后找到哈希表中索引吻合的数据存储表,然后直接在这个表中查询即可,而不用再遍历所有数据,这就是哈希表的好处。为了助记哈希表,我将使用一个例子来生动的阐述哈希表:在一个图书馆中,存放着很多各种类别的书,有小说,字典,杂志,专业书籍等若干本。在早期,图书管理员并不怎么好好打理这些书籍,它将这些书籍完全无序的堆放在一起,来了借阅的人,就要直接挨个翻找,直到找到自己想要看的书籍为止。在之后的某一天,来了一个新的图书管理员,这个图书管理员为这些书籍进行了分类,他按照这四种类别将这些书放到了不同的区域中,之后,来了借阅的人,首先会报出书名,然后图书管理员按照书名判断这本书的类别,如来了一个人想借阅《人民文学》,图书管理员就会告诉这名读者:该书籍属于杂志,请去杂志区寻找,这名读者就会直接步行至杂志区,这样就他就不用再对整堆书进行翻找,很大程度上的节省了时间。哈希表的工作原理,实际上就是这样的一种过程。其使用到的主要思想,就是索引存储,它按照某一种规则,为数据分类,然后将这些数据放入不同的类别下,这些类别会有相应的索引值,在我们进行查询的时候,首先会查询索引值,查询到索引值之后,便直接将这个数据与索引值对应的存储结构中进行查询,这样就直接缩减了查询范围,缩小了查询规模。

1.3 什么是哈希表

散列表(Hash table,也叫哈希表),是根据关键码值(key value)而直接进行访问的数据结构。也就是说,它通过把关键吗值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
在这里插入图片描述

2 应用实例

有一个公司,当新员工来报到时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的id时,要求查找到该员工所有信息。

要求:

不使用数据库,速度越快越好 => 哈希表
添加时,保证按照 id 从低到高插入。

(1)使用链表来实现哈希表,该链表不带表头【即:链表的第一个节点就存放雇员信息】
(2)思路分析
(3)代码实现
在这里插入图片描述
代码实现:

public class HashTabDemo {
    public static void main(String[] args) {
        // 创建哈希表
        HashTab hashTab = new HashTab(7);

        // 写一个简单菜单
        String key = "";
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("add:添加雇员");
            System.out.println("list:显示雇员");
            System.out.println("find:查找雇员");
            System.out.println("exit:退出系统");

            key = scanner.next();

            switch (key) {
                case "add":
                    System.out.println("输入id");
                    int id = scanner.nextInt();
                    System.out.println("输入名字");
                    String name = scanner.next();
                    // 创建雇员
                    Emp emp = new Emp(id, name);
                    hashTab.add(emp);
                    break;
                case "list":
                    hashTab.list();
                    break;
                case "find":
                    System.out.println("输入id");
                    int no = scanner.nextInt();
                    hashTab.findEmpById(no);
                    break;
                case "exit":
                    scanner.close();
                    System.out.println("退出系统");
                    System.exit(0);
                default:
                    break;
            }
        }


    }

}

// 创建 hashtable 管理多条链表
class HashTab {
    private EmpLinkedList[] empLinkedListArray;
    private int size;

    // 构造器
    public HashTab(int size) {
        this.size = size;
        // 初始化 empLinkedListArray
        empLinkedListArray = new EmpLinkedList[size];
        // 初始化每一个链表
        for (int i = 0; i < size; i++) {
            empLinkedListArray[i] = new EmpLinkedList();
        }
    }

    // 添加雇员
    public void add(Emp emp) {
        // 根据员工的id,得到该员工应当添加到哪条链表
        int empLinkedListNo = hashFun(emp.id);
        // 将 emp 添加到对应的链表中
        empLinkedListArray[empLinkedListNo].add(emp);

    }

    // 遍历所有的链表,遍历 hashtab
    public void list() {
        for (int i = 0; i < size; i++) {
            empLinkedListArray[i].list(i);
        }
    }


    // 编写散列函数
    public int hashFun(int id) {
        return id % size;
    }

    // 根据输入的 id查找雇员
    public void findEmpById(int id){
        // 使用散列函数确定到哪条链表查找
        int i = hashFun(id);
        Emp emp = empLinkedListArray[i].findEmpById(id);
        if(emp != null){
            System.out.printf("在第%d条链表中找到雇员 id=%d name=%s\n", i, emp.id, emp.name);
        }else{
            System.out.println("没有找到该雇员");
        }
    }


}


// 表示一个雇员
class Emp {
    public int id;
    public String name;
    public Emp next;

    public Emp(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

// 创建EmpLinkedList,表示链表
class EmpLinkedList {
    // 头指针,执行第一个Emp,因此我们这个链表的 head 是直接指向第一个 Emp
    // 默认为 null
    private Emp head;

    // 添加雇员到链表
    // 说明:
    // 1. 假定当添加雇员时,id是自增长,即id的分配总是从小到大
    // 因此,我们将该雇员直接加入到本链表的最后即可
    public void add(Emp emp) {
        // 如果是添加第一个雇员
        if (head == null) {
            head = emp;
            return;
        }
        // 如果不是第一个,则使用一个辅助的指针,帮助定位到最后
        Emp curEmp = head;
        while (true) {
            if (curEmp.next == null) {
                break;
            }
            // 后移
            curEmp = curEmp.next;
        }

        // 退出时直接将 emp 加入到链表
        curEmp.next = emp;

    }

    // 遍历链表的雇员信息
    public void list(int no) {
        // 说明链表为空
        if (head == null) {
            System.out.println("第" + no + "条链表为空!");
            return;
        }

        System.out.println("第" + no + "条链表的信息为");
        Emp curEmp = head;
        while (true) {
            System.out.printf("=> id=%d name =%s \t", curEmp.id, curEmp.name);
            // 说明 curEmp 已经是最后节点
            if (curEmp.next == null) {
                break;
            }
            // 后移
            curEmp = curEmp.next;

        }
        System.out.println();

    }

    // 根据id查找链表
    // 如果找到,返回Emp
    public Emp findEmpById(int id){
        // 判断链表是否为空
        if(head == null){
            System.out.println("链表为空");
            return null;
        }

        // 辅助指针
        Emp curEmp = head;
        while (true){
            // 找到
            if(curEmp.id == id){
                break;
            }
            // 遍历当前列表没有找到该雇员,退出条件
            if(curEmp.next == null){
                curEmp = null;
                break;
            }
            curEmp = curEmp.next;
        }

        return curEmp;

    }

}

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

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

相关文章

java+springboot+jsp农产品商城农场信息化系统多用户

系统功能包括前台&#xff1a;首页、商品信息、新闻资讯、我的、跳转到后台、购物车&#xff0c;管理员&#xff1a;个人中心、用户管理、员工管理、技术专家管理、部门信息管理、资金统计管理、农资信息管理、商品分类管理、商品信息管理、入库记录管理、出库记录管理、销售统…

如何调用api接口获取其中的数据

part1.API接口可以运用到的场景&#xff0c;主要包括以下几个方面&#xff1a; 1. 应用程序集成&#xff1a;API可以使不同的应用程序相互之间进行集成&#xff0c;比如将某个应用程序的数据传递给另一个应用程序&#xff0c;或者调用另一个应用程序的功能。 2. 数据共享&#…

cocos2dx游戏项目,集成到其他安卓项目工程之中!

背景 公司&#xff0c;想优化掉&#xff0c;在app中&#xff0c;以webview方式&#xff0c;加载游戏的方式。以安卓项目为例&#xff0c;改成&#xff1a;游戏项目导出安卓工程&#xff0c;可直接使用的aar资源。 第一步&#xff1a;cocos项目&#xff0c;构建安卓工程 安装…

icmp协议

1、icmp协议 2、工具之ping -c (设置ping的次数&#xff0c;默认无限次&#xff0c;可选) -i (设置ping的时间间隔&#xff0c;默认1秒&#xff0c;可选) -W (设置ping的超时时间&#xff0c;单位秒&#xff0c;可选) ping -c 3 -i 0.1 -W 0.1 www.baidu.com-i 自定义时间间…

接口自动化框架对比 | 质量工程

一、前言 自动化测试是把将手工驱动的测试行为转化为机器自动执行&#xff0c;通常操作是在某一框架下进行代码编写&#xff0c;实现用例自动发现与执行&#xff0c;托管在CI/CD平台上&#xff0c;通过条件触发或手工触发&#xff0c;进行回归测试&线上监控&#xff0c;代替…

opencv-python相机标定详解

文章目录 角点检测查看角点标定 opencv中内置了张正友的棋盘格标定法&#xff0c;通过一些姿态各异的棋盘格图像&#xff0c;就能标定相机的内外参数。 角点检测 第一步是角点检测&#xff0c;首先需要读取棋盘格图像 import numpy as np import cv2 import ospath imgs #…

一种Android应用的桌面图标隐藏方法

在Android10之前&#xff0c;应用程序通过调用PackageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)函数来实现图标隐藏。 但是在android10之后&#xff0c;所有带四大组件&#xff08…

RabbitMQ养成记 (6. spingboot 集成 rabbitMQ,生产者/消费者)

Springboot集成 搞springboot的那群人 不喜欢造轮子&#xff0c;就喜欢搞各种集成。 首先创建一个springboot项目&#xff1a; 之前我们在方法中 创建工厂类配置&#xff0c; 现在直接在application.yml 中配置即可&#xff1a; spring:rabbitmq:host: **********username:…

赢得浮生半日闲,内卷时代,我们需要怎样的智能科技?

哲学家罗泰戈拉说&#xff1a;“人是万物的尺度&#xff0c;是存在的事物存在的尺度。” 智能电器&#xff0c;究竟能为用户和市场提供什么样的价值&#xff1f;凭什么让消费者买单&#xff1f;毫无疑问&#xff0c;也应该以“人”为标尺。 今天&#xff0c;忙碌似乎成了现代人…

特征选择与特征提取

目录 一、 特征选择1、特征2、特征选择3、扩展——特征选择算法(有兴趣和精力可了解)拓展--完全搜索:拓展--启发式搜索:拓展--随机搜索:拓展--遗传算法: 二、 特征提取三、特征提取主要方法——PCA(主成分分析)1、PCA算法是如何实现的&#xff1f;PCA--零均值化&#xff08;中心…

Redis 概述

1. NoSQL 数据库简介 技术发展: 技术的分类 1、解决功能性的问题&#xff1a; Java、 Jsp、 RDBMS、 Tomcat、 HTML、 Linux、 JDBC、 SVN2、解决扩展性的问题&#xff1a; Struts、 Spring、 SpringMVC、 Hibernate、 Mybatis3、解决性能的问题&#xff1a; NoSQL、 Java 线…

从神经递质到网络:利用分子信息功能成像超越组织层级

导读 人脑在微观、介观和宏观尺度的组织原则上表现出复杂的相互作用。最近的协同多模态方法已经开始将微观尺度信息与系统水平的动力学联系起来&#xff0c;超越了组织层级&#xff0c;并为大脑的功能和功能障碍提供了新的视角。具体来说&#xff0c;可以将微观尺度特性&#…

HMI 自动生成技术讨论(1)

HMI 的信息模型 HMI 给人的基本印象就是一个设备控制面板&#xff0c;或者是控制室里电脑屏幕的图形控制界面。进入信息化时代&#xff0c;HMI 的需求越来越大&#xff0c;不仅仅是操作工人和运维人员需要HMI&#xff0c;集团公司&#xff0c;工厂&#xff0c;车间的管理人员&…

芯片封装技术(四)

集成电路芯片与封装之间是不可分割的整体。没有一个芯片可以不用封装就能正常工作&#xff0c;封装对芯片来说是必不可少的&#xff0c;随着IC生产技术的进步&#xff0c;封装技术也不断更新换代&#xff0c;每一代IC都与新一代的IC封装技术紧密相连。 一、什么是封装&#xf…

Qt文件系统源码分析—第二篇QFileInfo

深度 本文主要分析Windows平台&#xff0c;Mac、Linux暂不涉及 本文只分析到Win32 API/Windows Com组件/STL库函数层次&#xff0c;再下层代码不做探究 本文QT版本5.15.2 类关系图 QTemporaryFile继承QFile QFile、QSaveFile继承QFileDevice QFileDevice继承QIODevice Q…

考虑阶梯式碳交易机制与电制氢的综合能源系统热电优化

目录 1 主要内容 2 部分程序 3 程序结果 4 程序链接 1 主要内容 “双碳”背景下&#xff0c;为提高能源利用率&#xff0c;优化设备的运行灵活性&#xff0c;进一步降低综合能源系统&#xff08;IES&#xff09;的碳排放水平&#xff0c;提出一种IES低碳经济运行策略。首先…

【牛客刷题】笔试选择题整理(day1-day2)

每天都在进步呀 文章目录 1. 小数求模运算2. 进程的分区&#xff0c;这里说的不是JVM的分区。进程中&#xff0c;方法存放在方法区。3. 访问权限控制4. 继承与多态5. 与equals()6. 类加载顺序7. super()与this()7.1 super7.1.1 super调用父类构造方法7.1.2 super调用父类属性和…

Jabil EDI项目开源介绍

近期为了帮助广大用户更好地使用EDI系统&#xff0c;我们根据以往的项目实施经验&#xff0c;将成熟的EDI项目进行开源。用户安装好知行之桥EDI系统之后&#xff0c;只需要下载我们整理好的示例代码&#xff0c;并放置在知行之桥指定的工作区中&#xff0c;即可开始使用。 今天…

低代码工具库IRenderer『即可体验』上线了

在经历一段时间的折腾后&#xff0c;IRenderer迎来了一次版本更新&#xff0c;除了修复若干bug、优化编辑器性能外&#xff0c;在官网也添加了playground『即刻体验』入口。让您可以在线感受IRenderer可以提供的便利。 官网(github.io打开速度较慢&#xff0c;请稍等忍耐)地址…

SpringBoot——配置文件的分类

简单介绍&#xff1a; 在之前我们写配置文件的时候&#xff0c;我们直接在项目中的resources下面编写的配置文件&#xff0c;其实除了在这个路径下编写配置文件&#xff0c;还可以在其他的地方编写配置文件&#xff0c;并且不同位置的配置文件在启动的时候的优先级也是不一样的…