本文爲博主算法學習過程中的學習筆記,主要內容來源於其他平臺或書籍,出處請參考下方 參考&感謝 一節。
介紹
哈希表 是一種使用哈希函數組織數據,以支持快速插入和搜索的數據結構。
有兩種不同類型的哈希表:哈希集合 和 哈希映射。
- 哈希集合是 集合 數據結構的實現之一,用於存儲 非重複值。
- 哈希映射是 映射 數據結構的實現之一,用於存儲
(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。
如果您覺得文章還差了那麼點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?