Java 数据结构与算法-哈希表

Posted 学Java的冬瓜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 数据结构与算法-哈希表相关的知识,希望对你有一定的参考价值。

作者:学Java的冬瓜
博客主页:☀冬瓜的主页🌙
专栏【Java 数据结构与算法】
分享:曾梦想仗剑走天涯,看一看世界的繁华。——许巍《曾经的你》
主要内容:哈希方法,哈希函数,哈希表,哈希冲突,负载因子,降低哈希冲突,解决哈希冲突。


文章目录

一、什么是哈希表?

  • 在顺序结构和平衡树中,想要查找一个元素,必须先经过多次比较,才能找到。但是在哈希表中不一样。
  • 哈希表是一种数据结构,通过哈希函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系(即这个关键码通过哈希函数计算可以得到元素的存储位置,所以速度极快),这个存储结构就是哈希表。

二、哈希函数

  • 为了避免哈希冲突,有两种哈希函数方法比较普遍。

1、直接定制法

  • 取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
  • 优点:简单、均匀 ;缺点:需要事先知道关键字的分布情况
  • 使用场景:适合查找比较小且连续的情况
  • 面试题:【字符串中的第一个唯一字符】

2、除留取余法

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

三、哈希冲突

1、什么是负载因子?

负载因子也可称为荷载因子。

负载因子 α = 哈希表中元素个数 / 哈希表的长度

哈希表的长度没有扩容之前是定长的,当哈希表中元素的个数增加时,负载因子α就会变大,那么越容易出现哈希冲突,所以我们应该严格控制负载因子的大小,在 Java 中,限制了负载因子最大为 0.75,当超过了这个值,就要进行扩容哈希表,重新哈希(重新将各个元素放在新的位置上)。

2、怎么降低哈希冲突?

<1> 使用合适的哈希函数
<2> 增加散列表的长度。当负载因子越来越大时,冲突率越来越高。如果想要降低冲突率,就是要降低负载因子,那就增加散列表的长度。

3、怎么解决哈希冲突?

@ 闭散列

  • 闭散列又称开放地址法

开放地址法具体操作有以下两种:

集合:17459

线性探测

通过哈希函数获取待插入元素在哈希表中的位置
如果该位置中没有元素则直接插入新元素;
如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

二次探测:

首先H0经过key%m得到,然后如果冲突,就再次计算,直到算出不冲突的值。

@ 开散列(哈希桶 重点)

  • 开散列又称链地址法,哈希桶。
集合:17459
要插入:344484

  • 插入操作:JDK1.7之前,采用头插法;JDK1.8开始,使用尾插法。
  • 哈希表使用:数组+链表的形式,当链表长度超过 8且数组长度超过 64时,链表会变成红黑树
  • 那么就出现一个问题,当链表长度超过 8且数组长度超过 64,二者都满足时,链表才会变成红黑树。那有没有可能链表长度很长,但数组长度不超过64呢?
    没有可能!因为链表长度很长时,代表哈希冲突率很高了,那要降低哈希冲突,就需要降低负载因子(Java的系统库限制了负载因子为0.75),负载因子和数组长度成反比,那么就是得增大数组长度,这样当链表长度超过8,数组长度超过64时,链表就变成了红黑树。这样就把数组长度和链表长度联系起来了。
  • 哈希表查找时间复杂度:近似log(1)
    当链表长度比较短时,近似O(1)
    链表长度变长时,会通过加长数组来降低冲突率,从而使链表长度降低(数组长度增长后原来的链表会被打散),也近似O(1)
    当链表长度超过8,数组长度超过64时,复杂度为O(logn)。
    其实复杂度随着数组长度变化,链表长度的变化而变化。但是可以近似看成O(1)。

数据结构与算法-java-哈希表

什么是哈希表

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

 

其实可以这样理解,哈希表就像一个缓冲层

 

 

 下面来看一个案例

 

 大致可以分析如下

 

 

package hashTable;

import java.util.Scanner;

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

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

//表示链表
class EmpLinkedList{
    private  Emp head; //默认为null
    //add
    public void add(Emp emp)
    {
        //如果是添加第一个
        if(head ==null)
        {
            head=emp;
            return;
        }
        //不是第一个
        Emp temp = head;
        while (true)
        {
            if(temp.next==null)
            {
                //说明到最后了
                break;
            }
            temp=temp.next; //后移
        }
        //while退出时一定为最后,指向加入链表就行了
        temp.next=emp;
    }
    public void show(){
        if(head==null)
        {
            System.out.println("当前链表为空");
            return;
        }
        System.out.println("当前链表信息为");
        Emp temp=head;
        while (true)
        {
            System.out.printf("=>id=%d name=%s  \\t",temp.id,temp.name);
            if (temp.next == null) {

                break;
            }
            temp=temp.next;//后移即++
        }
        System.out.println();//换行
    }

}
//创建hash表,管理多条链表,如图那样
class HashTab{
    private EmpLinkedList [] empLinkedListArray;
    private int size;

    //构造器
    public HashTab(int size)
    {
        this.size=size;
        empLinkedListArray = new EmpLinkedList[size];
        for(int i=0;i<size;i++)
        {
            empLinkedListArray[i]=new EmpLinkedList();
        }
    }
    //添加员工
    public void  add(Emp emp)
    {
        int empNo=hashFunc(emp.id);
        empLinkedListArray[empNo].add(emp);
    }
    public void show(){
        for (int i=0;i<size;i++)
        {
            empLinkedListArray[i].show();
        }
    }
    public int hashFunc(int id)
    {
        return id%size;  //通过模判断在hash表的位置
    }
}


public class hashTableDemo {
    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("show:显示雇员");
            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 "show":
                    hashTab.show();
                    break;
                case "exit":
                    scanner.close();
                    break;
                default:
                    break;
            }

        }
    }
}

 

 

 

以上是关于Java 数据结构与算法-哈希表的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法-java-哈希表

Java数据结构与算法——哈希表

Java数据结构与算法——哈希表

学习数据结构笔记 ---[哈希表(Hash table)]

Java 数据结构 & 算法宁可累死自己, 也要卷死别人 9 哈希表原理

Java 数据结构 & 算法宁可累死自己, 也要卷死别人 9 哈希表原理