2021/06/07 散列表之除留余数法以及平方探测法

  • 给出一个序列:
    47 7 29 11 9 84 54 20 30  

    如何才能对它们进行排序并且要查找它的位置的时候特别方便?

散列表就是找出每一个数据的关键字并且按照关键字存放在特定的位置;

核心思想就是弄一个映射函数H(key),使得key的位置ret=H(key)

除留余数法是找出某个数字p,让位置ret=key%p;

当然很有可能在存放了几个之后就会有重叠位置的情况发生,也就是冲突。

这里解决冲突的方法是平方探测法:让这个重叠的初始位置ret去加Di;

Di是一段序列:1^2,-1^2,2^2,-2^2....  其中i是发生冲突的次数

例如说p=11.存放数字7的时候没问题(ret=7%11=7),然而存放29的时候(ret=29%11=7)

发生了冲突,那么第一次冲突就加1,也就是前进一格.8这个位置没有别的数据,不会冲突,所以存放在8;冲突解决

如果8这个位置也存放了其他的数字,那就是第二次冲突,这次要加D2也就是ret=(-1)=6;倒退一格

如果6这个位置也冲突那么就加D3也就是加4,前进四格...

循环下去直到找到空位置为止. 

这里关键是找到这里的“某个数字p”。除留余数法中的p指的其实是散列表的最大储存空间。一般是比原序列大的素数。取素数是为了减少冲突。

这里的p用Tablesize表示

它的算法是由Nextprime函数

<think>好的,我现在需要帮用户设计一个基于Java的散列表算法,使用职工姓名作为关键码,处理冲突的方法是开放定址法。首先,我得理清楚用户的需求。用户提到用除留余数法计算地址,那散列函数的设计应该是将姓名转换为一个数值,然后取模得到索引。不过,如何将拼音形式的姓名转换为数值呢? 接下来,处理冲突的方法使用开放定址法,具体是哪种探测方式呢?线性探测、平方探测还是双重散列?用户没有明确说明,可能需要选择最常见的方式,比如线性探测。不过需要确认用户是否有偏好,但这里可能默认线性探测。 然后,建表和查询功能的实现。首先,散列表的结构需要确定,比如数组的大小,这里可能需要一个合适的大小,最好是素数,这样可以减少冲突。用户可能没有指定表的大小,需要动态调整吗?或者固定大小?可能先假设用户需要一个固定大小的表,比如使用一个默认的容量,比如10007,这样是一个较大的素数,减少冲突概率。 接下来,散列函数的设计。职工姓名是拼音形式,比如“zhangsan”。需要将每个字符转换成ASCII码,然后相加或者进行某种组合。可能需要考虑如何将字符串转化为一个整数。常见的方法是将每个字符的ASCII值乘以一个权值,比如31的幂次,然后累加。例如,计算字符串的哈希值时,使用多项式累积:h = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]。但这里用户要求使用除留余数法,所以最终的哈希值是这个累积值对表大小取模。 但是,Java本身的String类的hashCode()方法就是使用类似的算法,所以可能直接使用字符串的hashCode(),然后取模。不过需要确保模的是表的大小,并且处理可能的负数。比如,hashCode()可能返回负数,所以需要取绝对值再模表大小。 然后处理冲突。当发生冲突时,使用开放定址法中的线性探测,即从冲突的位置开始,依次查找下一个可用的位置,比如index = (hash + i) % capacity,其中i从1开始递增,直到找到空的位置或遍历整个表。 接下来,构建散列表的结构。可能需要一个Entry类来保存键值对,这里键是姓名,值可能是职工的其他信息,但用户可能只需要存储姓名作为关键码,所以或许可以简化结构。或者,用户可能需要存储职工对象,但问题描述中没有具体说明,可能假设键是姓名,值可能关联其他数据,但示例中可以简化为存储姓名作为键。 实现插入方法时,计算哈希值,然后检查该位置是否被占用。如果被占用,就线性探测下一个位置,直到找到空的位置或已存在的同名键(可能需要处理重复的情况)。 查询方法类似,计算哈希值后,检查该位置,如果存在键匹配则返回,否则继续探测,直到找到空的位置或者遍历整个表。 需要注意的是,当表快满时,线性探测的效率会下降,可能需要考虑动态扩容,但用户可能只需要基础实现,所以暂时不考虑扩容,假设表足够大。 现在,编写Java代码。首先定义HashTable类,内部使用一个数组来存储Entry对象,Entry包含key和可能的value。这里可能简化,假设每个位置存储的是职工的姓名作为键,或者存储一个Employee对象。但根据用户需求,可能键是姓名,所以Entry类可以包含key(String)和value(可能其他信息,但用户可能只需要处理冲突,所以可能不需要存储额外数据,或者示例中可以存储一个占位符)。 不过用户的问题可能更关注结构而非存储数据,所以可能键就是职工姓名,值可以简单设为true或类似,或者存储职工对象。但为了示例,可能直接存储姓名作为键,并假设值不重要,或者使用一个简单的结构。 例如: class HashTable { private int capacity; private String[] table; // 或者使用Entry类,包含key和value // 但用户可能只需要处理冲突,所以可能用String数组即可,空位置为null,已占用的位置为姓名 public HashTable(int capacity) { this.capacity = capacity; table = new String[capacity]; } // 散列函数 private int hashFunction(String name) { int hash = name.hashCode(); return (Math.abs(hash) % capacity); } // 插入方法,处理冲突 public void insert(String name) { int index = hashFunction(name); int i = 0; while (i < capacity) { int currentIndex = (index + i) % capacity; if (table[currentIndex] == null || table[currentIndex].equals(name)) { // 假设允许重复插入或更新? table[currentIndex] = name; return; } i++; } throw new RuntimeException("HashTable is full"); } // 查询方法 public boolean search(String name) { int index = hashFunction(name); int i = 0; while (i < capacity) { int currentIndex = (index + i) % capacity; if (table[currentIndex] == null) { return false; } if (table[currentIndex].equals(name)) { return true; } i++; } return false; } } 不过这样可能存在一些问题。例如,当表中存在已删除的节点时,线性探测需要处理“墓碑”标记,否则查询可能提前终止。但用户的问题中没有提到删除功能,所以可能暂时忽略,假设不需要处理删除操作。 另外,散列函数是否正确处理了负数的hashCode?Math.abs可能有问题,因为当hashCode是Integer.MIN_VALUE时,Math.abs会溢出,导致负数。所以更安全的做法是取模后处理负数: return (hash % capacity + capacity) % capacity; 这样可以确保结果是非负的。 然后,测试代码,例如插入几个名字,然后查询是否存在。 不过用户可能需要更详细的实现,包括如何处理冲突,使用开放定址法,所以代码中的insert方法在冲突时会线性探测下一个位置。 此外,用户可能需要更完整的示例,比如主函数中的测试案例。 现在,总结步骤: 1. 设计散列函数:使用字符串的hashCode(),然后取模表的大小,处理负数。 2. 处理冲突:线性探测,即如果位置被占用,依次检查下一个位置。 3. 实现插入和查询方法,遵循开放定址法的线性探测。 可能还需要考虑表的大小是否为素数,以减少冲突。在构造函数中,可以检查传入的容量是否为素数,或者自动调整为最近的素数。但用户可能希望简单,所以示例中使用默认的素数容量,比如10007。 最后,生成相关问题,例如关于散列函数的选择、处理冲突的其他方法、性能分析等。</think>### 基于Java的散列表实现方案 #### 一、设计思路 1. **散列函数设计**:使用字符串多项式转换结合除留余数法 将职工姓名字符串转换为整数:$hash = (\sum_{i=0}^{n-1} s[i] \times 31^{n-1-i}) \bmod tableSize$ 其中$s[i]$为字符ASCII码,$n$为字符串长度[^1] 2. **冲突处理**:采用线性探测开放定址法 冲突时按公式计算新位置:$newIndex = (hash + probe) \bmod tableSize$,$probe$从1开始线性递增 #### 二、完整实现代码 ```java public class EmployeeHashTable { private static final int DEFAULT_CAPACITY = 10007; // 建议使用素数 private final String[] table; private final int capacity; public EmployeeHashTable() { this(DEFAULT_CAPACITY); } public EmployeeHashTable(int capacity) { this.capacity = capacity; this.table = new String[capacity]; } // 散列函数实现 private int hashFunction(String name) { int hash = 0; for (int i = 0; i < name.length(); i++) { hash = (31 * hash + name.charAt(i)) % capacity; } return (hash + capacity) % capacity; // 确保非负 } // 插入方法 public void insert(String name) { int index = hashFunction(name); int probe = 0; while (probe < capacity) { int currentIndex = (index + probe) % capacity; if (table[currentIndex] == null) { table[currentIndex] = name; return; } probe++; } throw new RuntimeException("Hash table overflow"); } // 查询方法 public boolean contains(String name) { int index = hashFunction(name); int probe = 0; while (probe < capacity) { int currentIndex = (index + probe) % capacity; if (table[currentIndex] == null) { return false; } if (table[currentIndex].equals(name)) { return true; } probe++; } return false; } } ``` #### 三、关键实现细节 1. **素数容量选择**: 使用10007作为默认表大小,该素数的选择能有效减少散列冲突。若需动态扩容,建议每次扩容为大于当前容量两倍的最小素数 2. **线性探测优化**: ```java // 改进的探测序列可降低聚集现象 int currentIndex = (index + probe*(probe+1)/2) % capacity; // 平方探测 ``` 3. **性能指标**: 装填因子$\alpha$应控制在0.7以下,当$\alpha = n/m > 0.7$时,查找成功平均比较次数约$\frac{1}{2}(1+\frac{1}{1-\alpha})$[^2] #### 四、测试用例 ```java public static void main(String[] args) { EmployeeHashTable ht = new EmployeeHashTable(); ht.insert("zhangsan"); ht.insert("lisi"); ht.insert("wangwu"); System.out.println(ht.contains("lisi")); // true System.out.println(ht.contains("zhaoliu")); // false } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值