哈希表的基础知识(含代码实现,及使用用例)

目录

什么是哈希表

哈希表的本质

键值对和Entry

哈希表存放数据

哈希函数是什么

哈希冲突

哈希冲突是什么

哈希冲突过高会怎么样

哈希冲突可以避免吗

降低冲突率的方法

1.设计良好的1哈希函数

2.负载因子(load factor)

如何解决哈希冲突

开放寻址法

链地址法

哈希表的实现代码 及 实现用例


什么是哈希表

哈希表也称为散列表,单词写作Hash table.

其次,哈希表是一种数据结构,既然是数据结构,那么哈希表有什么特点呢?

特点就是:哈希表可以根据键值(key)直接访问数据,查找数据更为高效.

哈希表的本质

哈希表是一种查找数据更高效的数据类型,因为他的底层实现就是在数组上,再进行加工,就称为哈希表。

键值对和Entry

哈希表经常存放的就是键值对,可以通过key取快速的访问value的值。

键值对就是key-value的组合,key的映射到value(就是一个值对应另一个值),key就称为键值,value就称为hash值。

在哈希表中就存放的是这些键值对,Entry就是我们jdk中给键值对起的别名。

一个Entry就是一个键值对。

哈希表存放数据

经过哈希函数计算后,会得到一个值,这个值就是Entry在数组中存放的具体下标值,注意这个Entry并不是一个简单的值,因为我们上边已经说了,Entry就相当于键值对,所以我们存放的这个Entry实际上是一个键值对(key-value)。哈希函数只是通过Key值计算Entry在数组中的存放位置。

哈希函数是什么

哈希函数就是将键值映射为存储位置的函数,简单说就是根据Key的值计算出Entry位置的函数。

哈希冲突

哈希冲突是什么

当两个不一样的key值 通过 哈希函数 计算,得到的Entry位置下标一样的时候就成为哈希冲突。

即key1 != key2,Entry(key1) = Entry(key2)。

哈希冲突过高会怎么样

哈希冲突过高,就相当于一个数组元素下标处存放了多个Entry,那么就会大大降低我们查找的效率。

哈希冲突可以避免吗

理论上哈希冲突是可以避免的,前提是哈希桶的数量要大于元素的数量,即数组的长度大于要存储的Entry的个数。

但是现实中,我们认为要存储的元素(Entry)是无穷的,但是哈希桶(数组长度)的个数是有限的,所以哈希冲突是无法避免的。

降低冲突率的方法

既然无法避免,那么我们肯定要寻找方法取降低冲突率。

1.设计良好的1哈希函数

直接定制法(适用于 元素数量 <= 哈希桶数量)

除留余数法(最常用):元素编号 % 哈希桶数量

2.负载因子(load factor)

已经存在于哈希表的元素数量 / 哈希桶的数量

负载因子和冲突率之间的函数关系:

通过影响负载因子,进而影响冲突率。

设定一个冲突率的阈值,计算处它对应的负载因子阈值。一旦负载因子>=负载因子的阈值,就扩容(增加哈希桶)。

如何解决哈希冲突

开放寻址法

简单来说就是位置被占用了,那就依次向后找,例如我们计算得出的位置是0,但是0有元素了,那么我们就向后找1位置,1位置没有元素我们就放在1的位置,若是1位置也有元素了,那就继续向后找2位置,依次类推,直到找到一个空的位置。

其实就是 寻找空位置:

上述是线性探测法(+1一次向后找一个)

还有二次探测法(+1,+4,+9向后找)

链地址法

就是每次存放好一个元素后,这时候这个位置存放的不单单是之前的那个Entry了,此时的Entry还额外的保存了一个next指针,如果这个位置再来元素的时候,就让这个next指针指向新来的元素的位置,他同样有一个新增的next指针。同理,再来新的就让空的next指针指向它,这样就形成了一个链表。

哈希表的实现代码 及 实现用例


public class MyHashMap {
    //哈希表由一个数组组成,保存的就是链表的头节点
    private Node[] array;//定义一个数组,存放哈希表的Entry
    private int size;//数组长度

    public MyHashMap(){
        this.array=new Node[8];
        this.size=0;
    }

    public static class Node{
        String key;
        Long value;
        Node next;//链地址法
        Node(String key,Long value){
            this.key=key;
            this.value=value;
        }
    }

    public int maxSize(){
        int max=Integer.MIN_VALUE;//取value最小值为max
        for(int i=0;i<array.length;i++){
            int count=0;
            Node cur = array[i];
            while(cur != null){
                count++;
                cur=cur.next;
            }
            if(count>max){
                max=count;
            }
        }
        return max;
    }

    public Long put(String key,Long value){
        //查找先看key是否存在
        int h=key.hashCode();
        //h只是一个>=0的整数(自然数)
        //把h变成[0,array.length)的取值,才能对应一个合法的下标
        //int index=j%array.length;//方法1:除留余数法
        //方法二JDK内部的HashMap使用的方法(前提:array.length总是2的幂次方
        //array.length in(2,4,8,16,32,64....)
        int h16=h>>16;
        //取h的高16位
        int l16=0xFF & h;
        //取h的低16位
        h=h16^l16;
        int index=h&(array.length-1);

        //根据下标找到对应的链表头节点
        Node head=array[index];
        Node cur=head;
        while(cur!=null){
            //比较key和cur.key是否相等
            //相等性用equals
            if(key.equals(cur.key)){
                //存在key
                //更新value
                Long oldValue=cur.value;
                cur.value=value;
                return oldValue;
            }
            cur=cur.next;
        }
        //没有找到key,进行插入
        Node node=new Node(key,value);
        //往head对应的链表插入,头插尾插都可以
        //JDK7用的是头插
        node.next=head;
        //head=node;   /head只是临时变量,不能折磨改
        array[index]=node;
        size++;

        if(1.0*size/array.length>=0.75){
            grow();
        }
        return null;
    }
    //扩容
    private void grow(){
        //扩容时候仍然要保证array.length是2的幂次方
        int newLength=array.length*2;
        Node[] newArray=new Node[newLength];

        //和是顺序表不同,不能简单的把数组的元素搬过来,
        // 元素e的下标是和array.length有关的
        //array.length 变了,下标可能会发生变化,所以需要每个key都需要重新计算
//        for(int i=0;i<array.length;i++){
//            newArray[i]=array[i];
//        }

        //如何把哈希表中的所有元素都遍历到
        for(int i=0;i<array.length;i++){
            Node head = array[i];
            Node cur = head;
            while(cur != null){
                Node next = cur.next;
                int h=cur.key.hashCode();
                int h16=h>>16;
                int l16=0xFF & h;
                h=h16^l16;
                int index=h&(newLength-1);

                //头插法
                cur.next=newArray[index];
                newArray[index]=cur;

                cur=next;
            }
        }
        this.array=newArray;
    }

    public Long get(String key){
        int h=key.hashCode();
        int h16=h>>16;
        int l16=0xFF & h;
        h=h16^l16;
        int index = h & (array.length-1);
        for(Node cur=array[index];cur!=null;cur=cur.next){
            if(key.equals(cur.key)){
                return cur.value;
            }
        }
        return null;
    }

    //删除操作
    public Long remove(String key){
        int h=key.hashCode();
        int h16=h>>16;
        int l16=0xFF & h;
        h=h16^l16;
        int index=h & (array.length - 1);

        Node prev=null;
        for(Node cur  = array[index];cur!=null;prev=cur,cur=cur.next){
            if (key.equals(cur.key)) {

                Long value=cur.value;
                if(prev==null){
                    array[index]=cur.next;

                }else{
                    prev.next=cur.next;
                }
                size--;
                return value;
            }
        }
        return null;
    }



}

import java.util.Random;

public class UseMyHashMap {
    private static final String chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    private static final Random rand=new Random();

    private static String randomString(){
        StringBuilder sb=new StringBuilder();
        //要写能修改的字符串就需要用到StingBuilder
        for(int i=0;i<100;i++){
            int idx=rand.nextInt(chars.length());
            char randChar=chars.charAt(idx);
            sb.append(randChar);
        }
        return sb.toString();

    }

    public static void main(String[] args){
        MyHashMap map=new MyHashMap();
        for(int i=0;i<1000;i++){
            String key=randomString();
            Long value=rand.nextLong();
            map.put(key,value);
        }
        System.out.println(map.maxSize());
    }

}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的哈希表设计 C 语言代码实现,仅供参考: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define SIZE 100 typedef struct { char* key; int value; } HashNode; typedef struct { HashNode** nodes; int size; } HashTable; HashTable* createHashTable(int size) { HashTable* hashtable = (HashTable*)malloc(sizeof(HashTable)); hashtable->nodes = (HashNode**)calloc(size, sizeof(HashNode*)); hashtable->size = size; return hashtable; } int hash(char* key, int size) { int hash = 0; int len = strlen(key); for (int i = 0; i < len; i++) { hash = (hash * 31 + key[i]) % size; } return hash; } void put(HashTable* hashtable, char* key, int value) { int hashValue = hash(key, hashtable->size); HashNode* node = hashtable->nodes[hashValue]; while (node != NULL) { if (strcmp(node->key, key) == 0) { node->value = value; return; } node = node->next; } node = (HashNode*)malloc(sizeof(HashNode)); node->key = key; node->value = value; node->next = hashtable->nodes[hashValue]; hashtable->nodes[hashValue] = node; } int get(HashTable* hashtable, char* key) { int hashValue = hash(key, hashtable->size); HashNode* node = hashtable->nodes[hashValue]; while (node != NULL) { if (strcmp(node->key, key) == 0) { return node->value; } node = node->next; } printf("Error: Key not found\n"); return -1; } void delete(HashTable* hashtable, char* key) { int hashValue = hash(key, hashtable->size); HashNode* node = hashtable->nodes[hashValue]; HashNode* prev = NULL; while (node != NULL) { if (strcmp(node->key, key) == 0) { if (prev == NULL) { hashtable->nodes[hashValue] = node->next; } else { prev->next = node->next; } free(node); return; } prev = node; node = node->next; } printf("Error: Key not found\n"); } void printHashTable(HashTable* hashtable) { for (int i = 0; i < hashtable->size; i++) { printf("%d: ", i); HashNode* node = hashtable->nodes[i]; while (node != NULL) { printf("(%s, %d) ", node->key, node->value); node = node->next; } printf("\n"); } } int main() { HashTable* hashtable = createHashTable(SIZE); put(hashtable, "apple", 3); put(hashtable, "banana", 2); put(hashtable, "orange", 1); printHashTable(hashtable); printf("Get value of apple: %d\n", get(hashtable, "apple")); delete(hashtable, "banana"); printHashTable(hashtable); return 0; } ``` 解释一下上述代码: - `HashTable` 结构体包一个 `nodes` 数组,用来存储哈希表中的节点,以及 `size` 表示哈希表的大小。 - `HashNode` 结构体包一个 `key` 字符串和一个 `value` 整数,表示键值对。 - `createHashTable` 函数创建一个哈希表,并返回其指针。 - `hash` 函数接收一个键和哈希表的大小,计算该键的哈希值,并返回对应哈希表中的位置。 - `put` 函数接收键值对,将其插入哈希表中。如果该键已经存在,则更新对应的值。 - `get` 函数接收一个键,返回对应的值。如果该键不存在,则输出错误信息,并返回 -1。 - `delete` 函数接收一个键,将其从哈希表中删除。如果该键不存在,则输出错误信息。 - `printHashTable` 函数打印哈希表的内容,用于调试。 - `main` 函数创建一个哈希表,插入若干键值对,然后调用各种函数测试其功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值