哈希算法 数据结构_实现哈希表构造和查找算法

哈希算法 数据结构_实现哈希表构造和查找算法一、什么是哈希表1.概述哈希表(Hashtable,也叫散列表),是根据关键码值(Keyvalue)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、什么是哈希表

1.概述

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

哈希算法 数据结构_实现哈希表构造和查找算法

通俗的理解一下:

  • 如果我们有n个元素要存储,那我们就用l个内存单元来存储他们
  • 然后我们有一个哈希函数f(x),我们把元素n用函数计算得到哈希值,也就是f(n)
  • f(n)就是存储元素n的那个内存单位的位置,也就是元素在l中的下标

2.为什么哈希表查询速度快

理解了哈希表的基本思路,我们也就不难理解为什么哈希表查询效率高了:

由于每个元素都能通过哈希函数直接计算获得地址,所以查找消耗时间非常少。

举个例子:

我们有哈希函数f(n)=n%3,现有元素{1,2,3},我们使用哈希函数分别获得其哈希值,并把哈希值作为下标存入一个数组,

也就是放f(1)=1,f(2)=2,f(3)=0,如果使用传统线性查找,需要遍历四次,而使用哈希函数计算并查找,只需要一步就能找到,

可以看得出,理想情况下,哪怕数列再长,找到某个元素都只需要一步。

3.哈希冲突

按照上文的例子,数列{1,2,3}通过哈希函数f(n)=n%3可以计算出哈希值,但是如果出现两个元素的哈希值相同就会出现哈希冲突,

比如f(1)和f(4)都会算出1,这个时候显然不可能上上面一样通过一个一维数组直接存储。

对此我们有两种方法,即开放地址法和分离链表法:

  • 开放地址法:如果某一哈希值对应的位置已经被占用了,就找另一个没被占用的位置。

    1. 开放地址法容易产生堆积问题;不适于大规模的数据存储
    2. 插入时可能会出现多次冲突的现象,而删除时如果元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较复杂
    3. 结点规模很大时会浪费很多空间

    注:关于开放地址法,具体可以参考这篇文章

  • 分离链表法:将散列表的每一个单元都扩展成为一个链表,相同哈希值的元素会被存储在同一个链表中。

    1. 分离链表法处理冲突简单,且无堆积现象,平均查找长度短
    2. 链表中的结点是动态申请的
    3. 相对开放地址法更加节省空间
    4. 插入与删除结点比较方便

在jdk8中,使用的就是分离链表法,当哈希冲突超过一点的限制,链表会转为红黑树。

二、代码实现

在这里我们实现一个基于分离链表法的哈希表:

1.节点类

/**
 * @Author:huang
 * @Date:2020-06-20 10:19
 * @Description:节点
 */
public class Node {

    //节点序号
    int num;

    //下一个节点
    Node next;

    public Node(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Node{" + "num=" + num + '}';
    }
}

2.单链表

/**
 * @Author:黄成兴
 * @Date:2020-06-20 10:19
 * @Description:单链表
 */
public class SingleLinkList {

    private Node head = new Node(0);

    public boolean isEmpty() {
        return head.next == null;
    }

    /**
     * 添加节点到链表
     * @param node 要插入的节点
     */
    public void add(Node node) {
        Node temp = head;
        //遍历链表
        while (true) {
            if (temp.next == null) {
                break;
            }
            //不是尾节点就继续遍历下一个节点
            temp = temp.next;
        }
        //将尾节点指向即将插入的新节点
        temp.next = node;
    }

    /**
     * 展示链表
     */
    public void show() {
        //判断链表是否为空
        if (isEmpty()) {
            return;
        }
        Node temp = head.next;
        //遍历链表
        while (true) {
            if (temp == null) {
                break;
            }
            System.out.println(temp.toString());
            temp = temp.next;
        }
    }

    /**
     * 根据序号获取节点
     * @param num 要获取的节点序号
     * @return
     */
    public Node get(int num){
        //判断链表是否为空
        if (isEmpty()) {
            return null;
        }
        Node temp = head.next;
        //遍历链表
        while (true) {
            if (temp == null) {
                return null;
            }
            if (temp.num == num) {
                return temp;
            }
            temp = temp.next;
        }
    }

    /**
     * 修改节点
     * @param node 要更新的节点
     */
    public void update(Node node) {
        Node temp = head;
        //判断链表是否为空
        if (isEmpty()) {
            return;
        }
        //获取要更新的节点序号
        int nodeNum = node.num;
        //遍历链表
        while (true) {
            //如果已经遍历完链表
            if (temp == null) {
                throw new RuntimeException("编号为" + temp.num + "的节点不存在!");
            }
            //如果找到了该节点
            if (temp.num == nodeNum) {
                return;
            }
            //继续遍历下一节点
            temp = temp.next;
        }
    }

    /**
     * 删除节点
     * @param num 要删除的节点编号
     */
    public void delete(int num) {
        Node temp = head;
        //判断链表是否为空
        if (isEmpty()) {
            return;
        }
        //遍历链表
        while (true) {
            //如果链表到底了
            if (temp.next == null) {
                return;
            }
            //如果找到了待删除节点的前一个节点
            if (temp.next.num == num) {
                //判断待删除节点是否为尾节点
                if (temp.next.next == null){
                    temp.next = null;
                }else {
                    temp.next = temp.next.next;
                }
                return;
            }
            //继续遍历下一节点
            temp = temp.next;
        }
    }
}

3.哈希表

/**
 * @Author:黄成兴
 * @Date:2020-07-04 11:36
 * @Description:哈希表
 */
public class HashTable {

    //数组长度
    private int size;
    //用于存放数据的数组
    private SingleLinkList[] arr;

    public HashTable(int size) {
        this.size = size;
        //初始化数组
        arr = new SingleLinkList[size];
        //初始化链表
        for (int i = 0; i < size; i++) {
            arr[i] = new SingleLinkList();
        }
    }

    /**
     * 获取哈希值
     * @param item
     * @return
     */
    public int getHashCode(int item) {
        return item % 2;
    }

    /**
     * 插入元素
     * @param item
     */
    public void insert(int item) {
        //获取哈希值
        int hashCode = getHashCode(item);
        //判断哈希值是否超过数组范围
        if (hashCode >= size || hashCode < 0) {
            throw new RuntimeException("哈希值:" + hashCode + "超出初始化长度!");
        }
        //如果该元素在链表中不存在就插入
        if (arr[hashCode].isEmpty() || arr[hashCode].get(item) == null) {
            //插入元素
            arr[hashCode].add(new Node(item));
        }else {
            //否则就更新
            arr[hashCode].update(new Node(item));
        }

    }

    /**
     * 查找元素
     * @param item
     */
    public Node get(int item) {
        //获取哈希值
        int hashCode = getHashCode(item);
        //判断哈希值是否超过数组范围
        if (hashCode >= size || hashCode < 0) {
            return null;
        }
        //查找元素
        return arr[hashCode].get(item);
    }

    /**
     * 删除元素
     * @param item
     */
    public void delete(int item) {
        //获取哈希值
        int hashCode = getHashCode(item);
        //删除元素
        arr[hashCode].delete(item);
    }

    /**
     * 展示某个哈希值对应链表的全部数据
     * @param item
     */
    public void show(int item) {
        //获取哈希值
        int hashCode = getHashCode(item);
        arr[hashCode].show();
    }

    /**
     * 展示哈希表的所有数据
     */
    public void showAll() {
        for (int i = 0; i < arr.length; i++) {
            //只展示非空链表
            if (!arr[i].isEmpty()) {
                System.out.println("第"+i+"条链表:");
                arr[i].show();
            }
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170809.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Linux服务.NO6——http协议

    Linux服务.NO6——http协议9.http9.1.http概念http协议即超文本传输协议,用于从万维网服务器传输超文本到本地浏览器的传送协议。http是基于TCP/IP通信协议来传递数据的一个属于应用层的面向对象的协议。http协议工作于c/s架构,浏览器作为客户端通过url向http服务端(即web服务器)发送所有请求,web服务器根据受到的请求后,向客户端发送响应。9.2.http特点1.简单快速:客户向服务…

    2025年7月24日
    3
  • 【sshd】sshd_config 中 PermitRootLogin 的forced-commands-only的限定密钥登陆、限定执行命令

    【sshd】sshd_config 中 PermitRootLogin 的forced-commands-only的限定密钥登陆、限定执行命令主讲:PermitRootLogin的可选项众所周知,sshd_config是sshd的配置文件,其中PermitRootLogin可以限定root用户通过ssh的登录方式,如禁止登陆、禁止密码登录、仅允许密钥登陆和开放登陆,以下是对可选项的概括:参数类别 是否允许ssh登陆 登录方式 交互shell yes 允许 没有限制 没有限制 without-password 允许 除密码以外 没有限制 forced-commands-on

    2022年6月1日
    30
  • C++中this指针的本质

    C++中this指针的本质一直以来对C++中的this不理解,只知道在构造函数中,如果构造函数的参数和类成员的名字一样的话,就可以用this指针来区分,如:this->a=a;一直以来都有这个疑问:this究竟是什么?我们明明没有定义这个this,但是我们可以直接用而编译器不会报错。今天来解决这个疑问。从刚才的代码中,我们用”this->”而不是”this.”就说明this是一个指针,而我们知道,在C、C++中,指针

    2022年5月16日
    46
  • oracle数字类型num比较大小,关于类型:Oracle NUMBER比较

    oracle数字类型num比较大小,关于类型:Oracle NUMBER比较通常,在编程中,不应比较浮点数据类型的相等性,因为存储的值通常是近似值。由于两个非整数的OracleNUMBER值存储方式不同(基数为10),是否可以可靠地比较它们的相等性?是的,OracleNUMBER类型是精确的。与浮点/双精度类型相比,它们更像是带刻度的整数。因此NUMBER(10,3)具有10位数字,在小数点后3位,这实际上是10位整数,小数位数为3。实际上,这就是JavaBig…

    2022年7月24日
    32
  • 企业联合体的形式_企业联合体「建议收藏」

    企业联合体的形式_企业联合体「建议收藏」企业联合体企业联合体企业联合体定义企业联合体定义::属于经济联合体的一种联合体表现形式(最大经济联合体是欧盟),完全区别于联合公司和战略合作伙伴的联合体,具体是集团公司或者是交叉参股的公司组建的一个企业(联合体)。该联合体应大于两家组建企业。企业联合体职能企业联合体职能::企业联合体可具有独立的法人代表、独立的品牌体系、独立的运作系统,企业联合体也可以是整合资源、共建平台的非独立企业联合体;共同建…

    2025年6月2日
    3
  • spi,i2c,uart三种总线的区别_i2c接口是什么意思

    spi,i2c,uart三种总线的区别_i2c接口是什么意思一、SPI I2CUART通信速率比较:SPI&gt;I2C&gt;UART1、同步通信&gt;异步通信;2、同步通信时必须有一根时钟线连接传输的两端;3、都是串行通信方式,并行通信用于内部存储间的通信,如flash;4、适合传输的距离和通信速率成反比关系;3-SPI:两条合一的数据线、1时钟线、1CS(设备片选线) SPI:2数据线、1时钟线、1CS(设备片选线)/串行同步通信…

    2025年11月15日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号