哈希表原理及简单设计

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

介绍

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

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

  • 哈希集合是 集合 数据结构的实现之一,用于存储 非重复值
  • 哈希映射是 映射 数据结构的实现之一,用于存储(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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章