【数据结构】HashTable 哈希表

在这里插入图片描述

前言

散列表(Hash table,也叫哈希表),使用哈希函数(Hash Function)将键(Key)转换为数组的索引,根据Key value而直接进行访问的数据结构,以此来实现快速的插入、删除和查找操作。它通过把关键码值映射到表中一个位置来访问记录(类似索引),以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

  1. 哈希函数:
    哈希函数是一个将任意长度的键转换为固定长度索引的算法。
    理想的哈希函数应该满足以下条件:
    快速计算:哈希函数的计算时间应尽可能短。
    均匀分布:对于不同的键,哈希函数应该产生均匀分布的索引,以减少冲突的可能性。
  2. 冲突处理:
    冲突(Collision)是指两个不同的键经过哈希函数计算后得到相同的索引。
    处理冲突的方法有多种,常见的包括开放寻址法(Open Addressing)和链地址法(Chaining):
    开放寻址法:当发生冲突时,寻找数组中的下一个空闲位置进行存储。这种方法通常会使用线性探测、二次探测或双散列等策略。
    链地址法:在数组的每个位置上存储一个链表,当发生冲突时,新来的元素将被添加到链表的末尾。
  3. 操作过程:
    插入:给定一个键值对,首先使用哈希函数计算出键的索引,然后在该索引位置存储值。如果发生冲突,则根据冲突处理策略进行调整。
    查找:给定一个键,使用哈希函数计算出其索引,然后在该索引位置查找对应的值。如果使用链地址法,可能需要遍历链表来找到正确的键值对。
    删除:给定一个键,使用哈希函数计算出其索引,然后在该索引位置删除对应的值。如果使用链地址法,可能需要遍历链表来找到并删除正确的键值对。
  4. 性能:
    在理想情况下,哈希表的插入、删除和查找操作的时间复杂度都是O(1),即常数时间复杂度,这是因为可以直接通过哈希函数计算出键的索引。
    然而,实际情况下,由于冲突的存在,哈希表的性能可能会下降。良好的哈希函数和冲突处理策略能够最大限度地减少冲突,从而保持高效的性能。
  5. 应用:
    哈希表在计算机科学中有广泛的应用,包括数据库索引、缓存、集合(如Java中的HashMap和HashSet)等领域。
    总的来说,哈希表是一种基于哈希函数实现的高效数据结构,通过将键映射到数组的索引,实现了快速的插入、删除和查找操作。尽管冲突是哈希表的一个固有问题,但通过精心设计的哈希函数和冲突处理策略,可以有效地管理冲突并保持优秀的性能。

数据结构源码

实现类

我们定义完整的哈希表 HashTable.java



import java.util.TreeMap;

public class HashTable<K, V> {

    private static final int upperTol = 10;

    private static final int lowerTol = 2;

    private static final int initCapacity = 7;

    private TreeMap<K, V>[] hashtable;

    private int M;

    private int size;

    public HashTable(int M) {
        this.M = M;
        size = 0;
        hashtable = new TreeMap[M];
        for (int i = 0; i < M; i++) {
            hashtable[i] = new TreeMap<>();
        }
    }

    public HashTable() {
        this(initCapacity);
    }

    private int hash(K key) {
        return (key.hashCode() & 0x7fffffff) % M;
    }

    public int getSize() {
        return size;
    }

    public void add(K key, V value) {
        TreeMap<K, V> map = hashtable[hash(key)];

        if (!map.containsKey(key)){
            map.put(key, value);
            size ++;

            if(size >= upperTol * M)
                resize(2 * M);
        }
    }

    public V remove(K key) {
        TreeMap<K, V> map = hashtable[hash(key)];
        V ret = null;
        if (map.containsKey(key)) {
            ret = map.remove(key);
            size--;

            if (size < lowerTol * M && M / 2 > 0)
                resize(M / 2);
        }
        return ret;
    }

    public void set(K key, V value) {
        TreeMap<K, V> map = hashtable[hash(key)];
        if (!map.containsKey(key)) {
            throw new IllegalArgumentException(key + " doesn't exist!");
        }
        map.put(key, value);
    }

    public boolean contains(K key) {
        return hashtable[hash(key)].containsKey(key);
    }

    public V get(K key) {
        return hashtable[hash(key)].get(key);
    }

    private void resize(int newM){
        TreeMap<K, V>[] newHashTable = new TreeMap[newM];
        for(int i = 0 ; i < newM ; i ++)
            newHashTable[i] = new TreeMap<>();

        for(int i = 0 ; i < M ; i ++)
            for(K key: hashtable[i].keySet())
                newHashTable[hash(key)].put(key, hashtable[i].get(key));

        this.M = newM;
        this.hashtable = newHashTable;
    }
}

数据结构拆解

之后对源码进行数据结构拆解

维护字段和内部类

我们定义维护字段和内部类


private static final int upperTol = 10;

private static final int lowerTol = 2;

private static final int initCapacity = 7;

private TreeMap<K, V>[] hashtable;

private int M;

private int size;

构造函数

定义构造函数:

public HashTable(int M) {
    this.M = M;
    size = 0;
    hashtable = new TreeMap[M];
    for (int i = 0; i < M; i++) {
        hashtable[i] = new TreeMap<>();
    }
}

public HashTable() {
    this(initCapacity);
}


public void add(K key, V value) {
    TreeMap<K, V> map = hashtable[hash(key)];

    if (!map.containsKey(key)){
        map.put(key, value);
        size ++;

        if(size >= upperTol * M)
            resize(2 * M);
    }
}

public V remove(K key) {
    TreeMap<K, V> map = hashtable[hash(key)];
    V ret = null;
    if (map.containsKey(key)) {
        ret = map.remove(key);
        size--;

        if (size < lowerTol * M && M / 2 > 0)
            resize(M / 2);
    }
    return ret;
}


public void set(K key, V value) {
    TreeMap<K, V> map = hashtable[hash(key)];
    if (!map.containsKey(key)) {
        throw new IllegalArgumentException(key + " doesn't exist!");
    }
    map.put(key, value);
}

private void resize(int newM){
    TreeMap<K, V>[] newHashTable = new TreeMap[newM];
    for(int i = 0 ; i < newM ; i ++)
        newHashTable[i] = new TreeMap<>();

    for(int i = 0 ; i < M ; i ++)
        for(K key: hashtable[i].keySet())
            newHashTable[hash(key)].put(key, hashtable[i].get(key));

    this.M = newM;
    this.hashtable = newHashTable;
}

private int hash(K key) {
    return (key.hashCode() & 0x7fffffff) % M;
}

public int getSize() {
    return size;
}

public boolean contains(K key) {
    return hashtable[hash(key)].containsKey(key);
}

public V get(K key) {
    return hashtable[hash(key)].get(key);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
为了设计一个哈,我们需要考虑以下几个方面: 1. 哈函数:用于将输入的数据映射到哈中的位置。这个函数应该是快速且均匀的,以保证哈的性能。对于这个问题,我们可以选择一个简单的哈函数,例如将每个人的姓名转换为 ASCII 码,并将它们相加。 2. 碰撞解决方案:由于哈函数不一定能够将每个输入映射到不同的位置,因此我们需要一种方法来解决碰撞。常见的方法包括链式哈和开放地址法。在这个问题中,我们可以使用链式哈,即在哈中的每个位置上存储一个链,以存储具有相同哈值的元素。 3. 哈大小:我们需要考虑哈的大小,以便在存储所有元素时不会浪费太多空间。对于这个问题,我们可以选择一个适当的大小,例如30,以存储30个人的信息。 基于以上三个方面,下面是一个简单的哈设计: ```python class Person: def __init__(self, name, age): self.name = name self.age = age class HashTable: def __init__(self): self.size = 30 self.table = [[] for _ in range(self.size)] def _hash(self, name): return sum(ord(c) for c in name) % self.size def insert(self, person): index = self._hash(person.name) self.table[index].append(person) def search(self, name): index = self._hash(name) for person in self.table[index]: if person.name == name: return person return None def delete(self, name): index = self._hash(name) for i, person in enumerate(self.table[index]): if person.name == name: del self.table[index][i] return True return False ``` 在上面的设计中,我们首先定义了一个Person类,用于存储每个人的姓名和年龄。然后,我们定义了一个HashTable类,其中包含一个哈和三个方法:insert、search和delete。insert方法用于将一个人添加到哈中,search方法用于查找具有给定姓名的人,delete方法用于从哈中删除具有给定姓名的人。 在具体实现中,我们首先在构造函数中定义了哈的大小和一个空的哈。然后,我们使用_hash方法来计算每个人的哈值,并将其插入到哈的正确位置。在search方法中,我们使用相同的哈函数来查找具有给定姓名的人,并在找到该人时返回它。在delete方法中,我们使用相同的哈函数来找到具有给定姓名的人,并在找到该人时从哈中删除它。 这只是一个简单的哈实现,实际应用中可能需要更复杂的实现来处理更多的情况。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

锥栗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值