哈希表原理及简单设计

Posted 却把清梅嗅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈希表原理及简单设计相关的知识,希望对你有一定的参考价值。

本文为博主算法学习过程中的学习笔记,主要内容来源于其他平台或书籍,出处请参考下方 参考&感谢 一节。

介绍

哈希表 是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。

有两种不同类型的哈希表:哈希集合哈希映射

  • 哈希集合是 集合 数据结构的实现之一,用于存储 非重复值
  • 哈希映射是 映射 数据结构的实现之一,用于存储(key, value)键值对。

通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现出色的性能。

原理及设计关键

哈希表的关键思想是使用哈希函数 将键映射到存储桶。更确切地说,

  • 1、当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中;
  • 2、当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。

1、哈希函数

哈希函数是哈希表中最重要的组件,该哈希表用于将键映射到特定的桶。

散列函数将取决于 键值的范围桶的数量

哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它需要在桶的数量和桶的容量之间进行权衡。

2、解决冲突

理想情况下,如果我们的哈希函数是完美的一对一映射,我们将不需要处理冲突。不幸的是,在大多数情况下,冲突几乎是不可避免的。

冲突解决算法应该解决以下几个问题:

1、如何组织在同一个桶中的值?
2、如果为同一个桶分配了太多的值,该怎么办?
3、如何在特定的桶中搜索目标值?

根据我们的哈希函数,这些问题与 桶的容量 和可能映射到 同一个桶的键的数目 有关。

让我们假设存储最大键数的桶有 N 个键。

通常,如果 N 是常数且很小,我们可以简单地使用一个数组将键存储在同一个桶中。如果 N 是可变的或很大,我们可能需要使用 高度平衡的二叉树 来代替。

设计哈希集合

不使用任何内建的哈希表库设计一个哈希集合。

在本文中,我们使用单独链接法,来看看它是如何工作的。

  • 1、从本质上讲,HashSet的存储空间相当于连续内存数组,这个数组中的每个元素相当于一个桶。
  • 2、给定一个值,我们首先通过哈希函数生成对应的散列值来定位桶的位置。
  • 3、一旦找到桶的位置,则在该桶上做相对应的操作,如add,remove,contains

对于桶的设计,我们有几种选择,可以使用数组来存储桶的所有值。然而数组的一个缺点是需要O(N)的时间复杂度进行插入和删除,而不是O(1)

因为任何的更新操作,我们首先是需要扫描整个桶为了避免重复。选择链表来存储桶的所有值是更好的选择,插入和删除具有常数的时间复杂度。

public class MyHashSet 

    private int keyRange;
    private Bucket[] buckets;

    /**
     * Initialize your data structure here.
     */
    public MyHashSet() 
        this.keyRange = 793;
        this.buckets = new Bucket[this.keyRange];

        for (int i = 0; i < keyRange; i++) 
            buckets[i] = new Bucket();
        
    

    protected int _hash(int key) 
        return key % this.keyRange;
    

    public void add(int key) 
        int index = this._hash(key);
        buckets[index].insert(key);
    

    public void remove(int key) 
        int index = this._hash(key);
        buckets[index].delete(key);
    

    public boolean contains(int key) 
        int index = this._hash(key);
        return buckets[index].contain(key);
    

    class Bucket 

        private LinkedList<Integer> container;

        Bucket() 
            this.container = new LinkedList<>();
        

        void insert(Integer key) 
            int index = container.indexOf(key);

            if (index == -1)
                container.addFirst(key);
        

        void delete(Integer key) 
            container.remove(key);
        

        boolean contain(Integer key) 
            return container.indexOf(key) != -1;
        
    

设计哈希映射

不使用任何内建的哈希表库设计一个哈希映射。

class MyHashMap 

    private int keyRange;
    private Node[] nodes;

    public MyHashMap() 
        this.keyRange = 793;
        this.nodes = new Node[this.keyRange];
    

    protected int _hash(int key) 
        return key % this.keyRange;
    

    public void put(int key, int value) 
        int index = this._hash(key);
        Node curr = nodes[index];

        if (curr == null) 
            Node node = new Node(key, value);
            nodes[index] = node;
            return;
        

        while (curr != null) 
            if (curr.key == key) 
                curr.value = value;
                return;
            
            if (curr.next == null) 
                Node node = new Node(key, value);
                node.prev = curr;
                node.next = curr.next;  // curr.next = null
                curr.next = node;
                return;
             else 
                curr = curr.next;
            
        
    

    public int get(int key) 
        int index = this._hash(key);
        Node curr = nodes[index];

        while (curr != null) 
            if (curr.key == key) 
                return curr.value;
             else 
                curr = curr.next;
            
        
        return -1;
    

    public void remove(int key) 
        int index = this._hash(key);
        Node curr = nodes[index];

        if (curr != null && curr.key == key) 
            Node next = curr.next;
            if (next != null)
                next.prev = null;
            nodes[index] = next;
            return;
        

        while (curr != null) 
            if (curr.key == key) 
                Node prev = curr.prev;
                Node next = curr.next;

                if (prev != null)
                    prev.next = next;
                if (next != null)
                    next.prev = prev;
                return;
             else 
                curr = curr.next;
            
        
    

    class Node 

        Integer key;
        Integer value;

        Node next;
        Node prev;

        Node(Integer key, Integer value) 
            this.key = key;
            this.value = value;
        
    

参考 & 感谢

文章绝大部分内容节选自LeetCode,概述:

  • https://leetcode-cn.com/explore/learn/card/hash-table/203/design-a-hash-table/797/

例题:

  • https://leetcode-cn.com/problems/design-hashset/
  • https://leetcode-cn.com/problems/design-hashmap/

关于我

Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub

如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?

以上是关于哈希表原理及简单设计的主要内容,如果未能解决你的问题,请参考以下文章

哈希表原理及如何避免键值冲突法?

数据结构 哈希表建立

一致性哈希原理与应用

算法二分法 ① ( 二分法基本原理简介 | 二分法与哈希表对比 | 常见算法对应的时间复杂度 )

如何设计并实现一个线程安全的 Map

蓝书《哈希与哈希表》——知识整理