设计哈希集合(HashSet)(HashMap,BitMap)

705.设计哈希集合(HashSet)

不使用任何内建的哈希表库设计一个哈希集合(HashSet)。

实现 MyHashSet 类:

void add(key) 向哈希集合中插入值 key 。
bool contains(key) 返回哈希集合中是否存在这个值 key 。
void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
提示:

  • 0 <= key <=KaTeX parse error: Expected 'EOF', got '&' at position 5: 10^6&̲
  • 最多调用 $10^4 $ 次 add、remove 和 contains 。

题目分析

  1. 哈希: 把任意长度的输入 通过散列算法变换成固定长度的输出,该输出就是散列值.散列的空间通常是要小于输出的空间。

  2. 哈希集合(HashSet):实际上是一个HashMap实例,上面只放key;.

    HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值

  3. HashMap: hash桶存放key链表或红黑树存放value

Jdk1.8 HashMap结构

那么设计一个哈希集合就是这样:

  1. 找到合适的数据结构,适当的空间当作散列表。

  2. 选择哈希函数(保证哈希函数处理key是不存在冲突的)将key映射到散列表中

解法:

1.暴力法

1.一个大小为1000001( 题目中的数字最大为此)选择boolean数组易于随机存取。

  1. hash(key) = key (key本身就是在散列表中的位置,空间换时间)
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;
    }
}
  • 时间复杂度:O(1)O(1)
  • 空间复杂度:O(1)O(1)

2.链表来构建hashSet

  1. 用链表来存放结点,拉链法来处理冲突

用hashmap的原理,不过链表长度大于8就不用转换为红黑树了。

  1. 哈希函数的设计:key 找到索引

(1). 利用key的hashcode. hashcode是32位的

​ h = key.hashCode() 为第一步:取hashCode值
​ hash = h ^ (h >>> 16) 为第二步:高位参与运算

​ (n-1) & hash 就是下标

这里的 Hash 算法本质上就是三步:取key的 hashCode 值、根据 hashcode 计算出hash值、通过取模计算下标

image-20210112191920111

JDK1.8 为什么要 hashcode 异或其右移十六位的值?

因为在JDK 1.7 中扰动了 4 次,计算 hash 值的性能会稍差一点点。 从速度、功效、质量来考虑,JDK1.8 优化了高位运算的算法,通过hashCode()的高16位异或低16位实现:(h = k.hashCode()) ^ (h >>> 16)。这么做可以在数组 table 的 length 比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。

为什么 hash 值要与length-1相与?

  • 把 hash 值对数组长度取模运算,模运算的消耗很大,没有位运算快。
  • 当 length 总是 2 的n次方时,h& (length-1) 运算等价于对length取模,也就是 h%length,但是 & 比 % 具有更高的效率。

HashMap数组的长度为什么是 2 的幂次方?

这样做效果上等同于取模,在速度、效率上比直接取模要快得多。除此之外,2 的 N 次幂有助于减少碰撞的几率。如果 length 为2的幂次方,则 length-1 转化为二进制必定是11111……的形式,在与h的二进制与操作效率会非常的快,而且空间不浪费。

(2). 数并不算很多,table可以直接设置为一个素数,key直接%取模就好。

hash(key) = key % length;

class MyHashSet {
    static class Node {
	        private int key;
	        private Node next;
	        private Node(int key) {
	            this.key = key;
	        }
	    }
	 	private Node[] table;
	 	private static final int length = 1024;
	 	
	    /** Initialize your data structure here. */
	    public MyHashSet() {
	    	table = new Node[length];
	    }
	    
	    public void add(int key) {
	    	int index = hash(key);
	    	Node nv = table[index], tmp = nv;
	    	
	    	if(tmp!=null)  //判断是否直接在table[index] 的首位
	    	{
	    		while(tmp!=null)
	    		{
	    			if(tmp.key == key) return ; // 早已存在
	    			tmp = tmp.next;
	    		}
	    	}else
	    	{
	    		table[index] = new Node(key);   //table[index]不存在
	    		return ;
	    	}
	    	
	    	// 头插法
	    	Node f = new Node(key);
	    	f.next = nv;
	    	table[index] = f;
	    }
	    
	    public void remove(int key) {
	    	int index = hash(key);
	    	Node nv = table[index], tmp = nv;
	    	
	    	if(tmp!=null)
	    	{
	    		Node pre = null;
	    		while(tmp!=null)
	    		{ 
	    			if(tmp.key == key) {
	    				if(pre!=null)
	    					pre.next = tmp.next;   // 删掉tmp;
	    				else 
	    					table[index] = tmp.next;   //在table[index]上
	    				return;
	    			}  
	    			pre = tmp;
	    			tmp = tmp.next;
	    		}
	    		
	    	}
	    }
	    
	    /** Returns true if this set contains the specified element */
	    public boolean contains(int key) {
	    	int index = hash(key);
	    	Node nv = table[index], tmp = nv;
	   
	    	while(tmp!=null)
	    	{ 
	    		if(tmp.key == key) return true;
	    		tmp = tmp.next;
	    	}
	    	return false;
	    	
	    }
	    public int hash(int key)
	    {
	    	int h = Integer.hashCode(key);
	    	h = h ^ (h>>16);
	    	return h &(length-1);
	    }
}
  • 时间复杂度:由于没有扩容的逻辑,最坏情况下复杂度为 O(n)O(n),一般情况下复杂度为 O(1)O(1)
  • 空间复杂度:O(1)O(1)

3.bitmap解法

1.bitmap实现原理

在java中,一个int类型占32个比特, 一个int类型的每一位能表示一个数字的话,一个int就可以表示32个数字。

具体思路:假设一个数组int a[32]:

  • a[0] 可以表是 0-31
  • a[1] 可以表示 32-63
  • a[2]可以表示 64-95
2.设计bitmap
  1. 选择一个int数组 大小为31,251(31,251*32=1000032,大于本题的范围)

  2. hash 函数 hash(key) = key %32; 存放在bitmap[key/32]上的hash(key)位置上

    ​ 设置方法: bitmap[key/32] |= 1 << hash(key); (把 hash(key)位设置为1)

    ​ remove方法: bitmap[key/32] &= ~ (1<< hash(key) //b&a,从b中取指定位(a中为1的部分)

    contains 方法:((bitmap[loc]>>offset) & 1) == 1? true:false; (验证(bitmap[loc]的offset位置上是否为1)

    位运算详解

class MyHashSet {
  private static final int length = 31251 ;
	private int[] bitmap ;
    /** Initialize your data structure here. */
    public MyHashSet() {
    	bitmap = new int[length];
    }
    
    public void add(int key) {   
    	if(!contains(key))
    	setVal(key/32,key%32,true);
    }
    
    public void remove(int key) {
    	if(contains(key))
    	setVal(key/32,key%32,false);
    }
    
    /** Returns true if this set contains the specified element */
    public boolean contains(int key) {
    	int loc = key/32, offset = key%32;
        return ((bitmap[loc]>>offset) & 1)  == 1? true:false;
    }
    public void setVal(int loc,int offset,boolean val)
    {
    	if(val)
    	{
    		//设置为1
    		bitmap[loc] |= 1<<offset ;
    	}else
    		bitmap[loc]  &= ~(1<<offset);
    }
     
}

时间复杂度: O(1)

空间复杂度: O(1)

706.设计hashmap

和上题的链表法一样加个value就ok

class MyHashMap {
    static class Node {
	        private int key;
	        private Node next;
	        private int val;
	        private Node(int key,int val) {
	            this.key = key;
	            this.val = val;
	        }
	    }
	 	private Node[] table;
	 	private static final int length = 4096;
	 	
	    /** Initialize your data structure here. */
	    public MyHashMap() {
	    	table = new Node[length];
	    }
	    
	    public void put(int key, int value) {
	    	int index = hash(key);
	    	Node nv = table[index], tmp = nv;
	    	
	    	if(tmp!=null)  //判断是否直接在table[index] 的首位
	    	{
	    		while(tmp!=null)
	    		{
	    			if(tmp.key == key){ tmp.val = value; return ; }// 早已存在
	    			tmp = tmp.next;
	    		}
	    		
	    	}else
	    	{
	    		table[index] = new Node(key,value);   //table[index]不存在
	    		return ;
	    	}
	    	
	    	// 头插法
	    	Node f = new Node(key,value);
	    	f.next = nv;
	    	table[index] = f;
	    }
	    
	    public void remove(int key) {
	    	int index = hash(key);
	    	Node nv = table[index], tmp = nv;
	    	
	    	if(tmp!=null)
	    	{
	    		Node pre = null;
	    		while(tmp!=null)
	    		{ 
	    			if(tmp.key == key) {
	    				if(pre!=null)
	    					pre.next = tmp.next;   // 删掉tmp;
	    				else 
	    					table[index] = tmp.next; 
	    				return;
	    			}  
	    			pre = tmp;
	    			tmp = tmp.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 = hash(key);
	    	Node nv = table[index], tmp = nv;
	   
	    	while(tmp!=null)
	    	{ 
	    		if(tmp.key == key) return tmp.val;
	    		tmp = tmp.next;
	    	}
	    	return -1;

	    }
	   
	    public int hash(int key)
	    {
	    	int h = Integer.hashCode(key);
	    	h = h ^ (h>>16);
	    	return h &(length-1);
	    	
	    }
}

/**
 * Your MyHashMap object will be instantiated and called as such:
 * MyHashMap obj = new MyHashMap();
 * obj.put(key,value);
 * int param_2 = obj.get(key);
 * obj.remove(key);
 */

在这里插入图片描述

  • 时间复杂度:由于没有扩容的逻辑,最坏情况下复杂度为 O(n),一般情况下复杂度为 O(1)O(1)
  • 空间复杂度:O(1)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值