哈希表是一种数据结构,它使用哈希函数组织数据,以支持快速插入和搜索
哈希表的原理
1.当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中;
2.当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。
插入
当我们用y=x%5作为哈希函数时,
当插入key0 时 key0就放到 bucket0中
1987和2就放到了bucket2中
24就放到了bucket4中
搜索
如果我们搜索 1987,我们将使用相同的哈希函数将1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。
如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。
设计哈希表的关键
在上面的y=x%5散列函数中,我们可以看出 x是键值,y是分配桶的索引,
散列函数将取决于键值的范围和桶的数量。
哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它***需要在桶的数量和桶的容量之间进行权衡***。最好是每个桶里里面的数量能够大致差不多。
题目1
不使用任何内建的哈希表库设计一个哈希集合
具体地说,你的设计应该包含以下的功能
add(value):向哈希集合中插入一个值。
contains(value) :返回哈希集合中是否存在这个值。
remove(value):将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
注意:
所有的值都在 [0, 1000000]的范围内。
操作的总数目在[1, 10000]范围内。
不要使用内建的哈希集合库。
package com.youyou;
/**
* leetcood中
* 执行用时 :33 ms, 在所有 java 提交中击败了81.40% 的用户
* 内存消耗 :60 MB, 在所有 java 提交中击败了77.66%的用户
*
* 所有的值都在 [0, 1000000]的范围内。
* 操作的总数目在[1, 10000]范围内。
* 不要使用内建的哈希集合库
* 思路用双维数组 以y=key/bucketSize作为冲突
*/
public class MyHashSet {
int bucketSize=10000;
int size=100;
int [][]array=new int[100][10000];
boolean flag=false;
public MyHashSet() {
}
public void add(int key) {
if(key==0){
flag=true;
return;
}
key=key-1;
int bucket1=key/bucketSize;
int bucket2=key%bucketSize;
array[bucket1][bucket2]=key+1;
}
public void remove(int key) {
if(key==0){
flag=false;
return;
}
key=key-1;
int bucket1=key/bucketSize;
int bucket2=key%bucketSize;
array[bucket1][bucket2]=0;
}
/** Returns true if this set contains the specified element */
public boolean contains(int key) {
if(key==0){
return flag;
}
key=key-1;
int bucket1=key/bucketSize;
int bucket2=key%bucketSize;
return key+1==array[bucket1][bucket2];
}
}
/**
* 别人的解题思路
* 执行用时 :41 ms, 在所有 java 提交中击败了75.36% 的用户
* 内存消耗 :52.8 MB, 在所有 java 提交中击败了95.39%的用户
* 内存上已经很好了
* 数组加双向链表,y = x % length 作为哈希函数
*/
public class MyHashSet2 {
class Node{
int val;
Node prev, next;
Node (int val) {
this.val = val;
}
}
private int length = 100;
private Node[] data = new Node[length];
/** Initialize your data structure here. */
public MyHashSet2() {
}
public void add(int key) {
int index = key % length;
Node curr = data[index];
if (curr == null) {
Node node = new Node(key);
data[index] = node;
return;
}
while(true) {
if (curr.val == key) {
return;
}
if(curr.next == null) {
Node node = new Node(key);
node.prev = curr;
curr.next = node;
return;
} else {
curr = curr.next;
}
}
}
public void remove(int key) {
int index = key % length;
Node curr = data[index];
if (curr != null && curr.val == key) {
Node next = curr.next;
if (next != null) {
next.prev = null;
}
data[index] = next;
return;
}
while(curr != null) {
if (curr.val == key) {
Node next = curr.next;
Node prev = curr.prev;
if (next != null) {
next.prev = prev;
}
if (prev != null) {
prev.next = next;
}
return;
}
curr = curr.next;
}
}
/** Returns true if this set contains the specified element */
public boolean contains(int key) {
int index = key % length;
Node curr = data[index];
while(curr != null) {
if (curr.val == key) {
return true;
}
curr = curr.next;
}
return false;
}
}
这个方法太叼了
/** Initialize your data structure here.
执行用时 :24 ms, 在所有 java 提交中击败了96.34% 的用户
内存消耗 :54 MB, 在所有 java 提交中击败了95.04%的用户
*/
class MyHashSet {
/** Initialize your data structure here. */
boolean[] map = new boolean[1000005];
public MyHashSet() {
}
public void add(int key) {
map[key] = true;
}
public void remove(int key) {
map[key] = false;
}
/** Returns true if this set contains the specified element */
public boolean contains(int key) {
return map[key] == true;
}
}
不使用任何内建的哈希表库设计一个哈希映射
具体地说,你的设计应该包含以下的功能
put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
remove(key):如果映射中存在这个键,删除这个数值对。
所有的值都在 [1, 1000000]的范围内。
操作的总数目在[1, 10000]范围内。
不要使用内建的哈希库。
package com.youyou;
/**
*所有的值都在 [1, 1000000]的范围内。
*操作的总数目在[1, 10000]范围内。
*不要使用内建的哈希库。
* 按照上面的node方法自己写了一个 不知道为什么通不过。。。。
* 感觉没有什么问题
*/
public class MyHashMap {
class MapNode{
int key;
int value;
MapNode pre;
MapNode next;
public MapNode(int key, int value) {
this.key = key;
this.value = value;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public MapNode getPre() {
return pre;
}
public void setPre(MapNode pre) {
this.pre = pre;
}
public MapNode getNext() {
return next;
}
public void setNext(MapNode next) {
this.next = next;
}
}
MapNode [] nodes=new MapNode[100];
public MyHashMap() {
}
/** value will always be non-negative. */
public void put(int key, int value) {
int index=key%100;
MapNode curr=nodes[index];
if (curr==null){
curr=new MapNode(key,value);
nodes[index]=curr;
return;
}
while (curr!=null){
if (curr.getKey()==key){
curr.setValue(value);
return;
}
if (curr.getNext()==null){
MapNode next=new MapNode(key,value);
curr.setNext(next);
next.setPre(curr);
return;
}
curr=curr.next;
}
}
/** 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 index=key%100;
MapNode curr=nodes[index];
if (curr==null){
return -1;
}
while (curr!=null){
if (curr.getKey()==key){
return curr.getValue();
}
curr=curr.next;
}
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 index=key%100;
MapNode curr=nodes[index];
if (curr==null){
return;
}
while (curr!=null){
if (curr.getKey()==key){
MapNode pre=curr.getPre();
MapNode next=curr.getNext();
if (pre!=null){
pre.setNext(next);
}
//这部分有问题 如果删除第一个节点,并且有后续节点,需要将next设置为第一个节点
if (next!=null){
next.setPre(pre);
}
if (pre==null&&next==null){
nodes[index]=null;
}
//找了大半个小时,终于找到了问题所在,需要添加下面一份代码
if (pre==null&&next!=null){
nodes[index]=next;
}
return;
}
curr=curr.next;
}
}
}
复杂度分析
当桶的大小足够小时,桶的数量足够多时,插入和搜索的时间复杂度都是 O(1)
但在最坏的情况下,桶大小的最大值将为 N。插入时时间复杂度为 O(1),搜索时为 O(N)。