哈希表原理及簡單設計

本文爲博主算法學習過程中的學習筆記,主要內容來源於其他平臺或書籍,出處請參考下方 參考&感謝 一節。

介紹

哈希表 是一種使用哈希函數組織數據,以支持快速插入和搜索的數據結構。

有兩種不同類型的哈希表:哈希集合哈希映射

  • 哈希集合是 集合 數據結構的實現之一,用於存儲 非重複值
  • 哈希映射是 映射 數據結構的實現之一,用於存儲(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

如果您覺得文章還差了那麼點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?

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