哈希表简介

目录

一、哈希表的引出

1、例题

2、哈希表以及哈希函数

3、哈希冲突 

二、基于开散列方式实现的哈希表 

1、哈希表的基本内容

2、哈希表的添加操作

3、哈希表的扩容操作

 4、哈希表的查找操作

 5、哈希表的删除操作

三、相关代码


一、哈希表的引出

1、例题

字符串中第一个唯一的字符

哈希表简介_第1张图片

第一种思路,使用Map集合:

 先将字符串转换为字符数组,在Map集合中保存字符以及字符出现的次数,遍历Map集合取出次数为1所对应的key值,再次遍历字符串,找到对应的索引就解决了

public int firstUniqChar(String s) {
          char[] data=s.toCharArray();
        Map map=new HashMap<>();
        for (char i:data){
            map.put(i,map.getOrDefault(i,0)+1);
        }
        for (int i = 0; i < data.length; i++) {
            if (map.get(data[i]) == 1) {
                return i;
            }
        }
        return -1;
    }

将每个字符出现的次数保存到整型数组中,最后找到次数为1的字符即可

public int firstUniqChar(String s) {
          //由于s中只包含小写字母,因此就将每个字符出现的频次保存到整型数组中
        int[] arr=new int[26];
        //遍历字符串,将字符出现的频次保存到arr数组中
        for (int i = 0; i 

2、哈希表以及哈希函数

上述例题中的第二种方法的arr数组就是一个哈希表,每一个不重复的字符都和一个整型数字一一对应,按照规则(字符-‘a’)将每个字符转换为数字,这个转换操作就叫做哈希函数。

哈希表中需要一种方法将任意数据类型转换为数组的索引,这样的一种方法就称为哈希函数

哈希表就是基于数组的扩展,在数组中如果知道索引,就可以在O(1)的时间复杂度内找到该元素,哈希表体现了空间去换取时间的策略方法

例题:

在数组[9,5,2,7,3,6,8]中查找元素是否存在?

就建立一个长度为10的arr数组,遍历原数组,若元素存在就在arr数组对应的位置添加true

int[] arr=new int[10];

arr[9]=true;

arr[5]=true;

.........直到扫描完整个集合

此时要查询7是否存在,就判断arr[7]的值是否为true

在上述例题中,我们开辟了原数组最大值+1的新的哈希数组,但是当原数组的数字之间的跨度非常大时,或者包含负数,这种方法就不适用了,比如[9,100000,-34,30000000,44]这个数组来说,就没法创造一个数字一一对应的索引的哈希数组了。

3、哈希冲突 

(1)哈希冲突的定义

当原数组的数字跨度非常大时,就需要让数字和下标建立一个映射关系(hash函数),让跨度很大的一组数据转为跨度很小的一组数据 

哈希函数可以将任意数据转换为索引

对于数据跨度很大的原数组来说,一般来说我们将任意正整数映射为小区数字最常用的方法是“取模”

[10,20,30,40,50] 映射为[0,1,2,3,4]对原数组进行%4

10%4=2

20%4=0

30%4=2

40%4=0

50%4=2

 哈希冲突:不同的数据经过函数的计算后得到了相同的值

解决方法就是对原数组取模一个素数7就可以了

(2)哈希冲突的解决方法

①闭散列:当发生冲突时,找到冲突位置的旁边是否存在空闲位置,直到找到第一个空闲位置放入元素(好存难查更难删),当哈希表的冲突十分严重时,此时查找一个元素就从O(1)变成了遍历数组O(n)

哈希表简介_第2张图片

 ②开散列:若出现哈希冲突时,就让这个位置变为链表,此时的哈希表就是数组加链表

哈希表简介_第3张图片

 如果当前哈希表的某个元素位置,就比如19这个位置,恰好后面好多元素取模后都等于19,此时19所在的链表就会非常长,查找效率就会降低

解决方法:

       针对整个数组进行扩容(原先数组长度101,下载扩容到102,就会由原先的%101变成%202),此时很多原来同一个链表的元素就会被均分到新的位置,降低了哈希冲突

        将这两个冲突严重的链表再次变为新的哈希表或者二分搜索树,只处理冲突严重的部分就好了,

二、基于开散列方式实现的哈希表 

1、哈希表的基本内容

public class MyHashMap {
    //有效节点个数
    private int size;
    //实际存储元素的Node数组
    private Node[] hashtable;
    //取模数
    private int M;

    public MyHashMap(){
        //默认初始化容量
        this(16);
    }
    public MyHashMap(int init){
        //初始化容量
        this.hashtable=new Node[init];
        this.M=init;
    }

    /**
     * 对key值进行hash运算
     * @param key
     * @return
     */
    public int hash(int key){
        return Math.abs(key)%M;
    }
    
}

class Node{
    //对key进行hash运算
    int key;
    int value;
    //下一个节点
    Node next;

    public Node(int key, int value) {
        this.key = key;
        this.value = value;
    }
}

2、哈希表的添加操作

  /**
     * 将一堆键值对保存到哈希表中,若key存在就修改原来的键值对,返回修改前的元素
     * @param key
     * @param val
     */
    public int  add(int key,int val){
        //先对key取模,取模后的值就是索引
        int index=hash(key);
        //遍历index对应的链表,查看key值是否存在
        for (Node x=hashtable[index];x!=null;x=x.next){
            if (x.key==key){
                int oldval=x.value;
                x.value=val;
                return oldval;
            }
        }
        //此时整个链表中不包含相对应的key值,就头插到当前链表
        Node node=new Node(key,val);
        //头节点就是hashtable[index]
        node.next=hashtable[index];
        hashtable[index]=node;
        size++;
        return val;
    }

3、哈希表的扩容操作

那么该在什么时候进行扩容操作呢?

哈希冲突严重的时候,如何判断哈希冲突的严重程度,在此引入负载因子

负载因子(LoadFactor)=哈希表的有效元素个数/哈希表长度

负载因子的值越大,就说明冲突越严重,但是数组的的利用率较高(数组中存储的元素很多),反之负载因子越小,就说明冲突越小,数组的利用率越低(数组中存储的元素比较少)。负载因子就是在空间和时间中取平衡值。

对于数组的扩容来说,当数组长度*负载因子<=有效元素个数时,就需要扩容。

哈希表简介_第4张图片

 此时在哈希表的基本属性中加入负载因子,假设此时是0.75

   //负载因子
    private static final double LoadFactor=0.75;
/**
     * 将一堆键值对保存到哈希表中,若key存在就修改原来的键值对,返回修改前的元素
     * @param key
     * @param val
     */
    public int  add(int key,int val){
        //先对key取模,取模后的值就是索引
        int index=hash(key);
        //遍历index对应的链表,查看key值是否存在
        for (Node x=hashtable[index];x!=null;x=x.next){
            if (x.key==key){
                int oldval=x.value;
                x.value=val;
                return oldval;
            }
        }
        //此时整个链表中不包含相对应的key值,就头插到当前链表
        Node node=new Node(key,val);
        //头节点就是hashtable[index]
        node.next=hashtable[index];
        hashtable[index]=node;
        size++;
        //添加完元素后判断是否需要扩容
        if (size>=hashtable.length*LoadFactor){
            //扩容方法
            Expansion();
        }
        return val;
    }

    /**
     * 哈希表的扩容方法。默认让新数组的长度变为原来的一倍
     */
    private void Expansion() {
        //新数组长度时原来的一倍
        Node[] newHashTable=new Node[hashtable.length*2];
        //将原数组的所有元素搬移到新的数组,此时的取模值M变为了新数组长度
        this.M=newHashTable.length;
        //进行元素搬移操作
        //遍历哈希表数组
        for (int i = 0; i 

 4、哈希表的查找操作

 /**
     * 判断当前key是否在表中存在
     * @param key
     * @return
     */
    public boolean containskey(int key){
        int index=hash(key);
        for (Node x=hashtable[index];x!=null;x=x.next){
            if (x.key==key){
                return true;
            }
        }
        return false;
    }

    /**
     * 判断value是否存在
     * @param val
     * @return
     */
    public boolean containsvalue(int val){
        //全表扫描
        for (int i=0;i

 5、哈希表的删除操作

**
     * 哈希表的删除操作
     * @param key
     * @param val
     * @return
     */
    public boolean remove(int key,int val){
        int index=hash(key);
        //判断头节点是否时待删除节点
        Node head=hashtable[index];
        if (head.key==key&&head.value==val){
            //此时头节点就是待删除的节点
            hashtable[index]=head.next;
            head=head.next=null;
            size--;
            return true;
        }
        Node prev=head;
        while (prev.next!=null){
            if (prev.next.key==key&&prev.next.value==val){
                //此时prev恰好就是待删除节点的前驱
                Node cur=prev.next;
                prev.next=cur.next;
                cur=cur.next=null;
                size--;
                return true;
            }else {
                prev=prev.next;
            }
        }
        //当前节点在哈希表中找不到
        throw new NoSuchElementException("can't find node!cannot remove!");
    }

三、相关代码

https://gitee.com/ren-xiaoxiong/rocket_class_ds/tree/master/src/hashicon-default.png?t=M4ADhttps://gitee.com/ren-xiaoxiong/rocket_class_ds/tree/master/src/hash

你可能感兴趣的:(哈希表,哈希表,数据结构,java)