Skip to content

哈希冲突与扩容

以key取模运算来映射index为例,很容易发现存在哈希冲突的情况。

12836 % 100 = 36
20336 % 100 = 36

如何避免哈希冲突?不难发现在key构成的输入空间趋近于无限的前提下,哈希表的容量Capcity越大,发生哈希冲突的概率越低。因此扩容哈希表可以一定程度有效避免哈希冲突。

哈希扩容需要将原来的迁移到新的哈希表中,需要重新计算原本的key在哈希表中映射的位置,非常的耗时。通常情况下,编程语言都会为哈希表预留充足的空间防止频繁扩容造成性能损耗。存在一个衡量标准:负载因子,表示哈希冲突程度,该值为 哈希表元素数量和容量的比值,通常为0.75,超出后自动扩容2倍。

解决哈希冲突

1.链式地址

哈希表扩容某种意义上是牺牲一定性能的暂时的"权宜之计"。想更好地解决哈希冲突,优化哈希表的数据结构以在冲突情况下可以正常使用作为第一策略,必要时再进行扩容操作。以链表原理将发生冲突的hash(key)的元素链式存储(实现使用动态数组vec)。

优化hash的实现结构以更优地解决哈希冲的问题,本质上是将hash表看作一个大的“桶”,里面又存在若干个小“桶”,"桶是动态vec",每个小桶用于存储 经由“哈希函数”处理后发生的冲突的key,value组合,即把发生冲突的元素存在一起,再匹配key相等的实现冲突情况下能够正确访问。

以初始容量4,负载因子2/3,扩容倍率2为初始化参数,实现如下:

rust
#[derive(Debug,PartialEq,Clone)]
pub  struct Pair{
    key:i32,
    val:String
}

#[derive(Debug)]
pub  struct LinkListHash{
    size:usize,
    capacity:usize,
    load_rate:f32,
    extend_ratio:i32,
    bucket:Vec<Vec<Pair>>
}

impl LinkListHash {
    pub  fn new() -> LinkListHash{
        Self{
            size:0,
            capacity:4,
            load_rate:2.0/3.0,
            extend_ratio: 2,
            bucket: vec![vec![];4],
        }
    }
    //哈希函数
    fn hasn_func(&self,key:i32)->usize{
        key as  usize % self.capacity
    }
    //判断是否需要扩容
    fn is_extend(&self) -> bool{
        (self.size / self.capacity) as f32 >= self.load_rate
    }
    //扩容
    pub  fn extend_content(&mut self){
        //先暂存原本的bucket
        let old = self.bucket.clone();
        self.capacity *= self.extend_ratio as usize;
        self.bucket = vec![vec![];self.capacity];

        //扩容后要对“小桶”内的元素重新设置位置,否则key % capacity(capacity翻倍了)无法匹配之前的位置导致无法访问
        for bucket  in  old.iter(){
            for pair in bucket {
                let index = self.hasn_func(pair.key);
                self.bucket[index].push(pair.clone())
            }
        }
    }
    //添加元素
    pub  fn add(&mut self, key:i32, value:&str) {
        let index = self.hasn_func(key);
        //判断是否超载,若超载则扩容
        if  self.is_extend(){
            self.extend_content()
        }
        //判断是否已经存在相同的key,若存在则替换,否则return
        for  pair in self.bucket[index].iter_mut() {
            if pair.key == key {
                pair.val = value.to_string();
            }
            return;
        }
        //不存在该key,则添加到对应小桶的尾部
        self.bucket[index].push(
            Pair{
                key,
                val:value.to_string()
            }
        );
        self.size += 1;
    }

    //获取元素
    pub fn get(&self,key:i32)->Option<&Pair>{
        let index = self.hasn_func(key);
        for pair in self.bucket[index].iter() {
            if pair.key == key{
                return Some(pair)
            }
        }
        None
    }

    //删除元素
    pub  fn remove(&mut self,key:i32) -> Option<Pair>{
        let index = self.hasn_func(key);
        for (i,pair) in self.bucket[index].iter_mut().enumerate() {
            if pair.key == key  {
                let removed = self.bucket[index].remove(i);
                self.size -= 1;
                return Some(removed)
            }
        }
        None
    }
}

#[cfg(test)]

mod test{
    use super::*;
    #[test]
    //测试创建、新增、删除
    fn it_works(){
        let mut ls = LinkListHash::new();
        ls.add(12,"lily");
        let removed = ls.remove(12);
        assert_eq!(removed,Some(Pair{key:12,val:"lily".to_string()}));
        assert_eq!(ls.bucket,vec![vec![];4])
    }
    #[test]
    //测试添加相同key的元素、获取元素
    fn repeat(){
        let mut ls = LinkListHash::new();
        ls.add(12,"lily");
        ls.add(12,"jack");
        assert_eq!(ls.get(12),Some(&Pair{key:12,val:"jack".to_string()}))
    }
    #[test]
    //测试扩容
    fn extend(){
        let mut ls = LinkListHash::new();
        ls.add(12,"lily");
        ls.extend_content();
        assert_eq!(1,ls.size);
        assert_eq!(8,ls.capacity);
        assert_eq!(ls.get(12),Some(&Pair{key:12,val:"lily".to_string()}));
    }
}

2.开放寻址

...