Skip to content

Commit

Permalink
vault backup: 2023-12-11 19:57:21
Browse files Browse the repository at this point in the history
Affected files:
docs/01数据结构与算法/04哈希表.md
docs/01数据结构与算法/assets/04哈希表/链地址法.png
  • Loading branch information
givedrug committed Dec 11, 2023
1 parent 3c4504c commit 05484b3
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 29 deletions.
149 changes: 120 additions & 29 deletions docs/01数据结构与算法/04哈希表.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
## 知识点

### HashSet设计
### HashSet与HashMap设计

要设计一个HashSet主要考虑如下问题
首先设计一个考虑HashSet,主要考虑如下问题

**哈希函数**:能够将集合中任意可能的元素映射到一个固定范围的整数值,并将该元素存储到整数值对应的地址上。

**冲突处理**:由于不同元素可能映射到相同的整数值,因此需要在整数值出现「冲突」时,需要进行冲突处理。总的来说,有以下几种策略解决冲突:

- 链地址法:为每个哈希值维护一个链表,并将具有相同哈希值的元素都放入这一链表当中。

![](assets/04哈希表/链地址法.png)

- 开放地址法:当发现哈希值 $h$ 处产生冲突时,根据某种策略,从 $h$ 出发找到下一个不冲突的位置。例如,一种最简单的策略是,不断地检查 $h+1,h+2,h+3,…$ 这些整数对应的位置。
- 再哈希法:当发现哈希冲突后,使用另一个哈希函数产生一个新的地址。

Expand All @@ -19,52 +22,50 @@ class MyHashSet {
private static final int BASE = 769;
private LinkedList[] data;

/** Initialize your data structure here. */
/**
* Initialize your data structure here.
*/
public MyHashSet() {
data = new LinkedList[BASE];
for (int i = 0; i < BASE; ++i) {
data[i] = new LinkedList<Integer>();
}
}

public void add(int key) {
int h = hash(key);
Iterator<Integer> iterator = data[h].iterator();
while (iterator.hasNext()) {
Integer element = iterator.next();
if (element == key) {
for (Object element : data[h]) {
if ((Integer) element == key) {
return;
}
}
data[h].offerLast(key);
}

public void remove(int key) {
int h = hash(key);
Iterator<Integer> iterator = data[h].iterator();
while (iterator.hasNext()) {
Integer element = iterator.next();
if (element == key) {
for (Object element : data[h]) {
if ((Integer) element == key) {
data[h].remove(element);
return;
}
}
}

/** Returns true if this set contains the specified element */

/**
* Returns true if this set contains the specified element
*/
public boolean contains(int key) {
int h = hash(key);
Iterator<Integer> iterator = data[h].iterator();
while (iterator.hasNext()) {
Integer element = iterator.next();
if (element == key) {
for (Object element : data[h]) {
if ((Integer) element == key) {
return true;
}
}
return false;
}

private static int hash(int key) {
private int hash(int key) {
return key % BASE;
}
}
Expand All @@ -80,25 +81,115 @@ class MyHashSet {

**思考**:为什么哈希函数在取整操作时,需要选择一个质数呢?

在初等数学中有一个基本定理,任意一个大于1的自然数,要么本身就是质数,要么可以分解为几个质数之积,这种分解本身,具有唯一性。
> 在初等数学中有一个基本定理,任意一个大于1的自然数,要么本身就是质数,要么可以分解为几个质数之积,这种分解本身,具有唯一性。
>
> 数字的因子越多,取模后冲突的可能性就越大。而素数的因子恰好只有1和其本身,就非常适合用于解决冲突。
>
> 比如2,4,6,8,10,12这6个数
>
> 如果对6取余,得到2,4,0,2,4,0只会得到3种HASH值,6的因子有1,2,3,6。冲突会很多。
>
> 如果对7取余,得到2,4,6,1,3,5得到6种HASH值,而7的因子只有1,7。
>
> (即使1的因子最小,但是在实际中并不用,因为mod1相当于不解决冲突。而初始化的的数组就会非常大。)
>
> Hash的用途很多,我们在使用Ngnix做负载均衡的时候,同样用的也是Hash的方式。总的来说,要是数据分布均匀一些,在这种时候就可以考虑使用Hash的方式对数据进行处理。
>
> 但凡事都有利弊,对于取整操作选择一个质数,虽然降低了取模时冲突的可能性,但Java8官方并没有使用质数,而是将默认值设置为16,并且扩容之后,依然是2的幂,这是为什么呢?主要有两点好处:
>
> 1)计算hash值时,使用2的幂,可以直接通过取低位的固定位数的操作来计算,速度更快。比如计算26mod16,直接取11010(26)的后4位,1010(10)即为所得。
>
> 2)在扩容时,需要对所有元素进行重新哈希,使用2的幂,可以使得一部分元素(均匀分布下是一半的元素),不需要移动。比如,1010(10)在数组长度为16时,取1010(10)的后4位,得到1010(10),而在扩容后,长度变为32时,应取1010(10)的后5位,仍然得到1010(10),不需要移动位置。而且,对于需要移动的元素,其下一个位置也容易通过位运算来计算,比如26mod16为1010(10),26mod32为11010(26),只需要在旧的哈希值前面补1即可。
>
数字的因子越多,取模后冲突的可能性就越大。而素数的因子恰好只有1和其本身,就非常适合用于解决冲突。
接下来,考虑如何设计一个HashMap,和HashSet类似,只需要将存储的只由key改为一个key-value键值对即可:

比如2,4,6,8,10,12这6个数
```java
class MyHashMap {
private class Pair {
private int key;
private int value;

如果对6取余,得到2,4,0,2,4,0只会得到3种HASH值,6的因子有1,2,3,6。冲突会很多。
public Pair(int key, int value) {
this.key = key;
this.value = value;
}

如果对7取余,得到2,4,6,1,3,5得到6种HASH值,而7的因子只有1,7。
public int getKey() {
return key;
}

(即使1的因子最小,但是在实际中并不用,因为mod1相当于不解决冲突。而初始化的的数组就会非常大。)
public int getValue() {
return value;
}

Hash的用途很多,我们在使用Ngnix做负载均衡的时候,同样用的也是Hash的方式。总的来说,要是数据分布均匀一些,在这种时候就可以考虑使用Hash的方式对数据进行处理。
public void setValue(int value) {
this.value = value;
}
}

参考:[leetcode题解]( https://leetcode.cn/problems/design-hashset/solutions/)
private static final int BASE = 769;
private LinkedList[] data;

### HashMap设计
/**
* Initialize your data structure here.
*/
public MyHashMap() {
data = new LinkedList[BASE];
for (int i = 0; i < BASE; ++i) {
data[i] = new LinkedList<Pair>();
}
}

/**
* value will always be non-negative.
*/
public void put(int key, int value) {
int h = hash(key);
for (Object pair : data[h]) {
if (((Pair) pair).getKey() == key) {
((Pair) pair).setValue(value);
return;
}
}
data[h].offerLast(new Pair(key, value));
}

/**
* Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key
*/
public int get(int key) {
int h = hash(key);
for (Object pair : data[h]) {
if (((Pair) pair).getKey() == key) {
return ((Pair) pair).getValue();
}
}
return -1;
}

/**
* Removes the mapping of the specified value key if this map contains a mapping for the key
*/
public void remove(int key) {
int h = hash(key);
for (Object pair : data[h]) {
if (((Pair) pair).getKey() == key) {
data[h].remove(pair);
return;
}
}
}

private int hash(int key) {
return key % BASE;
}
}
```

参考:
1. [leetcode题解:HashSet](https://leetcode.cn/problems/design-hashset/solutions/)
2. [leetcode题解:HashMap](https://leetcode.cn/problems/design-hashmap/solutions/)

### LRU缓存设计
todo
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 05484b3

Please sign in to comment.