HashTable详解

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

哈希表结构示例

1、数组+链表

2、数组+二叉树(链表树化)
在这里插入图片描述

google公司的一个上机题:

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

要求:
不使用数据库,速度越快越好=>哈希表(散列)
添加时,保证按照id从低到高插入 课后思考:如果id不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?
使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
思路分析并画出示意图
代码实现[增删改查(显示所有员工,按id查询)]

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class HashTab {
    class Emp{
        public int id;
        public String name;
        public Emp next;

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

    class EmpLinkedList{//链表
        private Emp head;

        public void add(Emp emp){
            if(head==null){
                head=emp;
                return;
            }
            Emp curEmp=head;
            while (true){
                if(curEmp.next==null){
                    break;
                }
                curEmp=curEmp.next;
            }
            curEmp.next=emp;
        }

        public void list(){
            if(head==null){
                System.out.println("kong");
                return;
            }
            System.out.println("为");
            Emp curEmp=head;
            while (true){
                System.out.println(curEmp.id+curEmp.name);
                if (curEmp.next==null){
                    break;
                }
                curEmp=curEmp.next;
            }
            System.out.println("完了");
        }
    }
}

哈希表查找原理

在这里插入图片描述

哈希函数

哈希函数其实就是我们常说的哈希算法,主要应用在以下这几个方面:文件校验、数字签名、鉴权协议。常用的哈希算法有以下这些。
<1>MD5:MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。MD5是输入不定长度信息,输出固定长度128bits的算法。
<2>SHA-1:常用于HTTPS传输和软件签名
<3>SHA-2:SHA-224/SHA-256/SHA-384/SHA-512并成为SHA-2
<4>SHA-3:之前名为Keccak算法,是一个加密杂凑算法

哈希函数设计的足够好的话,就会减少哈希冲突的概率

如何避免哈希冲突

当不同的键生成了相同的索引的时候-----》 处理冲突------》拉链法和线性探索法。

拉链法

将大小为M的数组的每一个元素指向一个链表,链表中的每一个节点都存储散列值为该索引的键值对,这个就是拉链法。
”John Smith”和”Sandra Dee”通过哈希函数指向152这个索引该索引又指向了一个链表,在链表中依次存储了这两个字符串
该方法的基本思想就是选择足够大的M,使得所有的链表都尽可能的短小,以保证查找的效率。对采用拉链法的哈希表实现的查找分为两步,首先是根据散列值找到对应的链表,然后沿着链表的顺序找到相应的键。

在这里插入图片描述
线性探索法

线性探测法是开放寻址法解决哈希冲突的一种方法,基本原理为,使用大小为M的数组来保存N个键值对,其中M>N,我们需要使用数组中的空位来解决碰撞冲突
在这里插入图片描述

对照前面的拉链法,在该图中,”Ted Baker”有唯一哈希值153的,但是由于153被”Sandra Dee”占用了。而原先”Sandra Dee”和”John Smith”的哈希值都是152的,但是在对”Sandra Dee”进行哈希的是偶发现152已经被占用了,所以往下找发现153没有被占用,就将其存放在153。后面”Ted Baker”哈希到153上,发现被占用了,就会往下找,发现154没有被占用,所以将其存放到154上面。

开放寻址法中最简单的是线性探测法:当碰撞发生时即一个键的散列值被另外一个键占用时,直接检查散列表中下一个位置,即将索引值加1,这样的线性探测有三种结果:

<1>命中,该位置的键个被查找的键相同

<2>未命中,键为空

<3>继续查找,该位置的键和被查找的键不同

拉链法和线性探索法对比

那个这两种方法的性能上面有什么区别?对于拉链法,查找的效率在于链表的长度,一般我们应该保证长度的M/8-M/2之间,如果链表的长度大于M/2,我们可以扩充数组长度。如果长度在0~M/8时,我们可以缩短数组的长度。对于线性探索法,动态调整数组的大小需要对所有的值重新进行散列并插入新的表中

不管是拉链法还是散列法,这种动态调整链表或者数组的大小以提高查询效率的同时,还应该考虑动态改变链表或者数组大小的成本。散列表长度加倍的插入需要进行大量的探索,这种均摊成本很多时候需要考虑。

哈希碰撞

不管是采用拉链法还是开放寻址法解决冲突,在后面查找的时候都需要进行多次探测或者查找,在很多时候会使得哈希表的查找效率退化,而不再是常数时间。如下图描述的那样。
在这里插入图片描述
哈希攻击就是通过精心构造哈希函数,使得所有的键进过函数函数后都会映射到同一个或者几个索引上,将哈希表退化为一个单链表,这样哈希表的各种操作,比如插入、查找都会从O(1)退化到了链表的查找操作,这样会消耗大量的CPU资源,导致系统无法响应,从而达到拒绝服务供给(Denial of Service,DOS)的目的。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐